From 6d11dd5770862c68af456a464e523134bc00bbda Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 27 Jun 2012 13:43:37 +0200 Subject: [PATCH 001/119] Only log the sending of regular packets in sender_t When sender_t is used to send ESP packets this would otherwise cause an extreme amount of debug messages. With this change all messages sent via sender_t.send_no_marker() cause no extra DBG1 log message, but for debugging purposes the socket plugins do log the same message again with DBG2 for all packets. --- src/libcharon/network/sender.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libcharon/network/sender.c b/src/libcharon/network/sender.c index a919a0263..641dd5333 100644 --- a/src/libcharon/network/sender.c +++ b/src/libcharon/network/sender.c @@ -87,7 +87,6 @@ METHOD(sender_t, send_no_marker, void, src = packet->get_source(packet); dst = packet->get_destination(packet); - DBG1(DBG_NET, "sending packet: from %#H to %#H", src, dst); if (this->send_delay) { @@ -124,6 +123,8 @@ METHOD(sender_t, send_, void, /* if neither source nor destination port is 500 we add a Non-ESP marker */ src = packet->get_source(packet); dst = packet->get_destination(packet); + DBG1(DBG_NET, "sending packet: from %#H to %#H", src, dst); + if (dst->get_port(dst) != IKEV2_UDP_PORT && src->get_port(src) != IKEV2_UDP_PORT) { From f3fefb1847ebebe4caf21563f1b941416bbce4b2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 28 Jun 2012 14:32:52 +0200 Subject: [PATCH 002/119] Increase log verbosity when sending NAT keep-alives --- src/libcharon/sa/ike_sa.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index d9e4ca582..1e3d00f02 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -488,7 +488,7 @@ METHOD(ike_sa_t, send_keepalive, void, data.ptr[0] = 0xFF; data.len = 1; packet->set_data(packet, data); - DBG1(DBG_IKE, "sending keep alive"); + DBG1(DBG_IKE, "sending keep alive to %#H", this->other_host); charon->sender->send_no_marker(charon->sender, packet); diff = 0; } From c1830d26703fe7ff51d31b3b2aeb3e15bde66c25 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 28 Jun 2012 18:06:31 +0200 Subject: [PATCH 003/119] Added methods to bio_reader_t to read data from end of buffer --- src/libstrongswan/bio/bio_reader.c | 174 +++++++++++++++++++++++++---- src/libstrongswan/bio/bio_reader.h | 54 +++++++++ 2 files changed, 204 insertions(+), 24 deletions(-) diff --git a/src/libstrongswan/bio/bio_reader.c b/src/libstrongswan/bio/bio_reader.c index fce0d1aef..3a62bb541 100644 --- a/src/libstrongswan/bio/bio_reader.c +++ b/src/libstrongswan/bio/bio_reader.c @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -47,8 +50,38 @@ METHOD(bio_reader_t, peek, chunk_t, return this->buf; } -METHOD(bio_reader_t, read_uint8, bool, - private_bio_reader_t *this, u_int8_t *res) +/** + * A version of chunk_skip() that supports skipping from the end (i.e. simply + * reducing the size) + */ +static inline chunk_t chunk_skip_end(chunk_t chunk, size_t bytes, bool from_end) +{ + if (chunk.len > bytes) + { + if (!from_end) + { + chunk.ptr += bytes; + } + chunk.len -= bytes; + return chunk; + } + return chunk_empty; +} + +/** + * Returns a pointer to the data to read, optionally from the end + */ +static inline u_char *get_ptr_end(private_bio_reader_t *this, u_int32_t len, + bool from_end) +{ + return from_end ? this->buf.ptr + (this->buf.len - len) : this->buf.ptr; +} + +/** + * Read an u_int8_t from the buffer, optionally from the end of the buffer + */ +static bool read_uint8_internal(private_bio_reader_t *this, u_int8_t *res, + bool from_end) { if (this->buf.len < 1) { @@ -56,13 +89,16 @@ METHOD(bio_reader_t, read_uint8, bool, this->buf.len); return FALSE; } - *res = this->buf.ptr[0]; - this->buf = chunk_skip(this->buf, 1); + *res = *get_ptr_end(this, 1, from_end); + this->buf = chunk_skip_end(this->buf, 1, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint16, bool, - private_bio_reader_t *this, u_int16_t *res) +/** + * Read an u_int16_t from the buffer, optionally from the end + */ +static bool read_uint16_internal(private_bio_reader_t *this, u_int16_t *res, + bool from_end) { if (this->buf.len < 2) { @@ -70,13 +106,16 @@ METHOD(bio_reader_t, read_uint16, bool, this->buf.len); return FALSE; } - *res = untoh16(this->buf.ptr); - this->buf = chunk_skip(this->buf, 2); + *res = untoh16(get_ptr_end(this, 2, from_end)); + this->buf = chunk_skip_end(this->buf, 2, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint24, bool, - private_bio_reader_t *this, u_int32_t *res) +/** + * Read an u_int32_t (only 24-bit) from the buffer, optionally from the end + */ +static bool read_uint24_internal(private_bio_reader_t *this, u_int32_t *res, + bool from_end) { if (this->buf.len < 3) { @@ -84,13 +123,16 @@ METHOD(bio_reader_t, read_uint24, bool, this->buf.len); return FALSE; } - *res = untoh32(this->buf.ptr) >> 8; - this->buf = chunk_skip(this->buf, 3); + *res = untoh32(get_ptr_end(this, 3, from_end)) >> 8; + this->buf = chunk_skip_end(this->buf, 3, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint32, bool, - private_bio_reader_t *this, u_int32_t *res) +/** + * Read an u_int32_t from the buffer, optionally from the end + */ +static bool read_uint32_internal(private_bio_reader_t *this, u_int32_t *res, + bool from_end) { if (this->buf.len < 4) { @@ -98,13 +140,16 @@ METHOD(bio_reader_t, read_uint32, bool, this->buf.len); return FALSE; } - *res = untoh32(this->buf.ptr); - this->buf = chunk_skip(this->buf, 4); + *res = untoh32(get_ptr_end(this, 4, from_end)); + this->buf = chunk_skip_end(this->buf, 4, from_end); return TRUE; } -METHOD(bio_reader_t, read_uint64, bool, - private_bio_reader_t *this, u_int64_t *res) +/** + * Read an u_int64_t from the buffer, optionally from the end + */ +static bool read_uint64_internal(private_bio_reader_t *this, u_int64_t *res, + bool from_end) { if (this->buf.len < 8) { @@ -112,13 +157,16 @@ METHOD(bio_reader_t, read_uint64, bool, this->buf.len); return FALSE; } - *res = untoh64(this->buf.ptr); - this->buf = chunk_skip(this->buf, 8); + *res = untoh64(get_ptr_end(this, 8, from_end)); + this->buf = chunk_skip_end(this->buf, 8, from_end); return TRUE; } -METHOD(bio_reader_t, read_data, bool, - private_bio_reader_t *this, u_int32_t len, chunk_t *res) +/** + * Read a chunk of data from the buffer, optionally from the end + */ +static bool read_data_internal(private_bio_reader_t *this, u_int32_t len, + chunk_t *res, bool from_end) { if (this->buf.len < len) { @@ -126,11 +174,83 @@ METHOD(bio_reader_t, read_data, bool, this->buf.len, len); return FALSE; } - *res = chunk_create(this->buf.ptr, len); - this->buf = chunk_skip(this->buf, len); + *res = chunk_create(get_ptr_end(this, len, from_end), len); + this->buf = chunk_skip_end(this->buf, len, from_end); return TRUE; } +METHOD(bio_reader_t, read_uint8, bool, + private_bio_reader_t *this, u_int8_t *res) +{ + return read_uint8_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint16, bool, + private_bio_reader_t *this, u_int16_t *res) +{ + return read_uint16_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint24, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint24_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint32, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint32_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_uint64, bool, + private_bio_reader_t *this, u_int64_t *res) +{ + return read_uint64_internal(this, res, FALSE); +} + +METHOD(bio_reader_t, read_data, bool, + private_bio_reader_t *this, u_int32_t len, chunk_t *res) +{ + return read_data_internal(this, len, res, FALSE); +} + +METHOD(bio_reader_t, read_uint8_end, bool, + private_bio_reader_t *this, u_int8_t *res) +{ + return read_uint8_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint16_end, bool, + private_bio_reader_t *this, u_int16_t *res) +{ + return read_uint16_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint24_end, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint24_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint32_end, bool, + private_bio_reader_t *this, u_int32_t *res) +{ + return read_uint32_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_uint64_end, bool, + private_bio_reader_t *this, u_int64_t *res) +{ + return read_uint64_internal(this, res, TRUE); +} + +METHOD(bio_reader_t, read_data_end, bool, + private_bio_reader_t *this, u_int32_t len, chunk_t *res) +{ + return read_data_internal(this, len, res, TRUE); +} + METHOD(bio_reader_t, read_data8, bool, private_bio_reader_t *this, chunk_t *res) { @@ -202,6 +322,12 @@ bio_reader_t *bio_reader_create(chunk_t data) .read_uint32 = _read_uint32, .read_uint64 = _read_uint64, .read_data = _read_data, + .read_uint8_end = _read_uint8_end, + .read_uint16_end = _read_uint16_end, + .read_uint24_end = _read_uint24_end, + .read_uint32_end = _read_uint32_end, + .read_uint64_end = _read_uint64_end, + .read_data_end = _read_data_end, .read_data8 = _read_data8, .read_data16 = _read_data16, .read_data24 = _read_data24, diff --git a/src/libstrongswan/bio/bio_reader.h b/src/libstrongswan/bio/bio_reader.h index 85434a784..3162f3eda 100644 --- a/src/libstrongswan/bio/bio_reader.h +++ b/src/libstrongswan/bio/bio_reader.h @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -27,6 +30,8 @@ typedef struct bio_reader_t bio_reader_t; /** * Buffered input parser. + * + * @note Integers are returned in host byte order. */ struct bio_reader_t { @@ -93,6 +98,55 @@ struct bio_reader_t { */ bool (*read_data)(bio_reader_t *this, u_int32_t len, chunk_t *res); + /** + * Read a 8-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint8_end)(bio_reader_t *this, u_int8_t *res); + + /** + * Read a 16-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint16_end)(bio_reader_t *this, u_int16_t *res); + + /** + * Read a 24-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint24_end)(bio_reader_t *this, u_int32_t *res); + + /** + * Read a 32-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint32_end)(bio_reader_t *this, u_int32_t *res); + + /** + * Read a 64-bit integer from the end of the buffer, reduce remaining. + * + * @param res pointer to result + * @return TRUE if integer read successfully + */ + bool (*read_uint64_end)(bio_reader_t *this, u_int64_t *res); + + /** + * Read a chunk of len bytes from the end of the buffer, reduce remaining. + * + * @param len number of bytes to read + * @param res ponter to result, not cloned + * @return TRUE if data read successfully + */ + bool (*read_data_end)(bio_reader_t *this, u_int32_t len, chunk_t *res); + /** * Read a chunk of bytes with a 8-bit length header, advance. * From 59a15a7475f60049dc2b259935c2a047ec764f95 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 29 Jun 2012 10:12:27 +0200 Subject: [PATCH 004/119] Added a method to bio_writer_t that allows to extract the internal buffer --- src/libstrongswan/bio/bio_writer.c | 13 +++++++++++++ src/libstrongswan/bio/bio_writer.h | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/libstrongswan/bio/bio_writer.c b/src/libstrongswan/bio/bio_writer.c index bf373d6ac..16a16f4bd 100644 --- a/src/libstrongswan/bio/bio_writer.c +++ b/src/libstrongswan/bio/bio_writer.c @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -205,6 +208,15 @@ METHOD(bio_writer_t, get_buf, chunk_t, return chunk_create(this->buf.ptr, this->used); } +METHOD(bio_writer_t, extract_buf, chunk_t, + private_bio_writer_t *this) +{ + chunk_t buf = get_buf(this); + this->buf = chunk_empty; + this->used = 0; + return buf; +} + METHOD(bio_writer_t, destroy, void, private_bio_writer_t *this) { @@ -236,6 +248,7 @@ bio_writer_t *bio_writer_create(u_int32_t bufsize) .wrap24 = _wrap24, .wrap32 = _wrap32, .get_buf = _get_buf, + .extract_buf = _extract_buf, .destroy = _destroy, }, .increase = bufsize ? max(bufsize, 4) : 32, diff --git a/src/libstrongswan/bio/bio_writer.h b/src/libstrongswan/bio/bio_writer.h index 0b50f7882..8f84d0500 100644 --- a/src/libstrongswan/bio/bio_writer.h +++ b/src/libstrongswan/bio/bio_writer.h @@ -1,4 +1,7 @@ /* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2010 Martin Willi * Copyright (C) 2010 revosec AG * @@ -27,6 +30,8 @@ typedef struct bio_writer_t bio_writer_t; /** * Buffered output generator. + * + * @note Integers are converted to network byte order before writing. */ struct bio_writer_t { @@ -127,6 +132,14 @@ struct bio_writer_t { */ chunk_t (*get_buf)(bio_writer_t *this); + /** + * Return the encoded data buffer and detach it from the writer (resets + * the internal buffer). + * + * @return chunk to internal buffer (has to be freed) + */ + chunk_t (*extract_buf)(bio_writer_t *this); + /** * Destroy a bio_writer_t. */ @@ -136,6 +149,9 @@ struct bio_writer_t { /** * Create a bio_writer instance. * + * The size of the internal buffer is increased automatically by bufsize (or a + * default if not given) if the initial size does not suffice. + * * @param bufsize initially allocated buffer size */ bio_writer_t *bio_writer_create(u_int32_t bufsize); From 6dfdcf420f789797fddccaaa00c75c77b1d5be65 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 29 Jun 2012 10:47:20 +0200 Subject: [PATCH 005/119] Added a method to bio_writer_t that allows to skip a number of bytes A chunk pointing to the skipped bytes is returned, allowing users of bio_writer_t to write/copy data to the skipped bytes themselves. --- src/libstrongswan/bio/bio_writer.c | 15 +++++++++++++++ src/libstrongswan/bio/bio_writer.h | 9 +++++++++ 2 files changed, 24 insertions(+) diff --git a/src/libstrongswan/bio/bio_writer.c b/src/libstrongswan/bio/bio_writer.c index 16a16f4bd..8576843ee 100644 --- a/src/libstrongswan/bio/bio_writer.c +++ b/src/libstrongswan/bio/bio_writer.c @@ -202,6 +202,20 @@ METHOD(bio_writer_t, wrap32, void, this->used += 4; } +METHOD(bio_writer_t, skip, chunk_t, + private_bio_writer_t *this, size_t len) +{ + chunk_t skipped; + + while (this->used + len > this->buf.len) + { + increase(this); + } + skipped = chunk_create(this->buf.ptr + this->used, len); + this->used += len; + return skipped; +} + METHOD(bio_writer_t, get_buf, chunk_t, private_bio_writer_t *this) { @@ -247,6 +261,7 @@ bio_writer_t *bio_writer_create(u_int32_t bufsize) .wrap16 = _wrap16, .wrap24 = _wrap24, .wrap32 = _wrap32, + .skip = _skip, .get_buf = _get_buf, .extract_buf = _extract_buf, .destroy = _destroy, diff --git a/src/libstrongswan/bio/bio_writer.h b/src/libstrongswan/bio/bio_writer.h index 8f84d0500..57a5c3d38 100644 --- a/src/libstrongswan/bio/bio_writer.h +++ b/src/libstrongswan/bio/bio_writer.h @@ -125,6 +125,15 @@ struct bio_writer_t { */ void (*wrap32)(bio_writer_t *this); + /** + * Skips len bytes in the buffer before the next data is written, returns + * a chunk covering the skipped bytes. + * + * @param len number of bytes to skip + * @return chunk pointing to skipped bytes in the internal buffer + */ + chunk_t (*skip)(bio_writer_t *this, size_t len); + /** * Get the encoded data buffer. * From 64004973e37fece5231c583eca2c1defbd516cc9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 5 Jul 2012 13:44:57 +0200 Subject: [PATCH 006/119] Adding class to manage ESP context (crypto, sequence numbers) --- src/libipsec/Android.mk | 3 +- src/libipsec/Makefile.am | 3 +- src/libipsec/esp_context.c | 300 +++++++++++++++++++++++++++++++++++++ src/libipsec/esp_context.h | 110 ++++++++++++++ 4 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 src/libipsec/esp_context.c create mode 100644 src/libipsec/esp_context.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 99ff69106..d452793c4 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -3,7 +3,8 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ -ipsec.c ipsec.h +ipsec.c ipsec.h \ +esp_context.c esp_context.h # build libipsec --------------------------------------------------------------- diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 0b8faf724..df0c9acfa 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -1,7 +1,8 @@ ipseclib_LTLIBRARIES = libipsec.la libipsec_la_SOURCES = \ -ipsec.c ipsec.h +ipsec.c ipsec.h \ +esp_context.c esp_context.h libipsec_la_LIBADD = diff --git a/src/libipsec/esp_context.c b/src/libipsec/esp_context.c new file mode 100644 index 000000000..c7fb7ab2f --- /dev/null +++ b/src/libipsec/esp_context.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include + +#include "esp_context.h" + +#include +#include +#include +#include + +/** + * Should be a multiple of 8 + */ +#define ESP_DEFAULT_WINDOW_SIZE 128 + +typedef struct private_esp_context_t private_esp_context_t; + +/** + * Private additions to esp_context_t. + */ +struct private_esp_context_t { + + /** + * Public members + */ + esp_context_t public; + + /** + * Crypter used to encrypt/decrypt ESP packets + */ + crypter_t *crypter; + + /** + * Signer to authenticate ESP packets + */ + signer_t *signer; + + /** + * The highest sequence number that was successfully verified + * and authenticated, or assigned in an outbound context + */ + u_int32_t last_seqno; + + /** + * The bit in the window of the highest authenticated sequence number + */ + u_int seqno_index; + + /** + * The size of the anti-replay window (in bits) + */ + u_int window_size; + + /** + * The anti-replay window buffer + */ + chunk_t window; + + /** + * TRUE in case of an inbound ESP context + */ + bool inbound; +}; + +/** + * Set or unset a bit in the window. + */ +static inline void set_window_bit(private_esp_context_t *this, + u_int index, bool set) +{ + u_int i = index / CHAR_BIT; + + if (set) + { + this->window.ptr[i] |= 1 << (index % CHAR_BIT); + } + else + { + this->window.ptr[i] &= ~(1 << (index % CHAR_BIT)); + } +} + +/** + * Get a bit from the window. + */ +static inline bool get_window_bit(private_esp_context_t *this, u_int index) +{ + u_int i = index / CHAR_BIT; + + return this->window.ptr[i] & (1 << index % CHAR_BIT); +} + +/** + * Returns TRUE if the supplied seqno is not already marked in the window + */ +static bool check_window(private_esp_context_t *this, u_int32_t seqno) +{ + u_int offset; + + offset = this->last_seqno - seqno; + offset = (this->seqno_index - offset) % this->window_size; + return !get_window_bit(this, offset); +} + +METHOD(esp_context_t, verify_seqno, bool, + private_esp_context_t *this, u_int32_t seqno) +{ + if (!this->inbound) + { + return FALSE; + } + + if (seqno > this->last_seqno) + { /* |----------------------------------------| + * <---------^ ^ or <---------^ ^ + * WIN H S WIN H S + */ + return TRUE; + } + else if (seqno > 0 && this->window_size > this->last_seqno - seqno) + { /* |----------------------------------------| + * <---------^ or <---------^ + * WIN ^ H WIN ^ H + * S S + */ + return check_window(this, seqno); + } + else + { /* |----------------------------------------| + * ^ <---------^ + * S WIN H + */ + return FALSE; + } +} + +METHOD(esp_context_t, set_authenticated_seqno, void, + private_esp_context_t *this, u_int32_t seqno) +{ + u_int i, shift; + + if (!this->inbound) + { + return; + } + + if (seqno > this->last_seqno) + { /* shift the window to the new highest authenticated seqno */ + shift = seqno - this->last_seqno; + shift = shift < this->window_size ? shift : this->window_size; + for (i = 0; i < shift; ++i) + { + this->seqno_index = (this->seqno_index + 1) % this->window_size; + set_window_bit(this, this->seqno_index, FALSE); + } + set_window_bit(this, this->seqno_index, TRUE); + this->last_seqno = seqno; + } + else + { /* seqno is inside the window, set the corresponding window bit */ + i = this->last_seqno - seqno; + set_window_bit(this, (this->seqno_index - i) % this->window_size, TRUE); + } +} + +METHOD(esp_context_t, get_seqno, u_int32_t, + private_esp_context_t *this) +{ + return this->last_seqno; +} + +METHOD(esp_context_t, next_seqno, bool, + private_esp_context_t *this, u_int32_t *seqno) +{ + if (this->inbound || this->last_seqno == UINT32_MAX) + { /* inbound or segno would cycle */ + return FALSE; + } + *seqno = ++this->last_seqno; + return TRUE; +} + +METHOD(esp_context_t, get_signer, signer_t *, + private_esp_context_t *this) +{ + return this->signer; +} + +METHOD(esp_context_t, get_crypter, crypter_t *, + private_esp_context_t *this) +{ + return this->crypter; +} + +METHOD(esp_context_t, destroy, void, + private_esp_context_t *this) +{ + chunk_free(&this->window); + DESTROY_IF(this->crypter); + DESTROY_IF(this->signer); + free(this); +} + +/** + * Described in header. + */ +esp_context_t *esp_context_create(int enc_alg, chunk_t enc_key, + int int_alg, chunk_t int_key, bool inbound) +{ + private_esp_context_t *this; + + INIT(this, + .public = { + .get_crypter = _get_crypter, + .get_signer = _get_signer, + .get_seqno = _get_seqno, + .next_seqno = _next_seqno, + .verify_seqno = _verify_seqno, + .set_authenticated_seqno = _set_authenticated_seqno, + .destroy = _destroy, + }, + .inbound = inbound, + .window_size = ESP_DEFAULT_WINDOW_SIZE, + ); + + switch(enc_alg) + { + case ENCR_AES_CBC: + this->crypter = lib->crypto->create_crypter(lib->crypto, enc_alg, + enc_key.len); + break; + default: + break; + } + if (!this->crypter) + { + DBG1(DBG_ESP, "failed to create ESP context: unsupported encryption " + "algorithm"); + destroy(this); + return NULL; + } + if (!this->crypter->set_key(this->crypter, enc_key)) + { + DBG1(DBG_ESP, "failed to create ESP context: setting encryption key " + "failed"); + destroy(this); + return NULL; + } + + switch(int_alg) + { + case AUTH_HMAC_SHA1_96: + case AUTH_HMAC_SHA2_256_128: + case AUTH_HMAC_SHA2_384_192: + case AUTH_HMAC_SHA2_512_256: + this->signer = lib->crypto->create_signer(lib->crypto, int_alg); + break; + default: + break; + } + if (!this->signer) + { + DBG1(DBG_ESP, "failed to create ESP context: unsupported integrity " + "algorithm"); + destroy(this); + return NULL; + } + if (!this->signer->set_key(this->signer, int_key)) + { + DBG1(DBG_ESP, "failed to create ESP context: setting signature key " + "failed"); + destroy(this); + return NULL; + } + + if (inbound) + { + this->window = chunk_alloc(this->window_size / CHAR_BIT + 1); + memset(this->window.ptr, 0, this->window.len); + } + return &this->public; +} + + diff --git a/src/libipsec/esp_context.h b/src/libipsec/esp_context.h new file mode 100644 index 000000000..db247dced --- /dev/null +++ b/src/libipsec/esp_context.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup esp_context esp_context + * @{ @ingroup libipsec + */ + +#ifndef ESP_CONTEXT_H_ +#define ESP_CONTEXT_H_ + +#include +#include +#include + +typedef struct esp_context_t esp_context_t; + +/** + * ESP context, handles sequence numbers and maintains cryptographic primitives + */ +struct esp_context_t { + + /** + * Get the crypter. + * + * @return crypter + */ + crypter_t *(*get_crypter)(esp_context_t *this); + + /** + * Get the signer. + * + * @return signer + */ + signer_t *(*get_signer)(esp_context_t *this); + + /** + * Get the current outbound ESP sequence number or the highest authenticated + * inbound sequence number. + * + * @return current sequence number, in host byte order + */ + u_int32_t (*get_seqno)(esp_context_t *this); + + /** + * Allocate the next outbound ESP sequence number. + * + * @param seqno the sequence number, in host byte order + * @return FALSE if the sequence number cycled or inbound context + */ + bool (*next_seqno)(esp_context_t *this, u_int32_t *seqno); + + /** + * Verify an ESP sequence number. Checks whether a packet with this + * sequence number was already received, using the anti-replay window. + * This operation does not modify the internal state. After the sequence + * number is successfully verified and the ESP packet is authenticated, + * set_authenticated_seqno() should be called. + * + * @param seqno the sequence number to verify, in host byte order + * @return TRUE when sequence number is valid + */ + bool (*verify_seqno)(esp_context_t *this, u_int32_t seqno); + + /** + * Adds a sequence number that was successfully verified and + * authenticated. A user MUST call verify_seqno() immediately before + * calling this method. + * + * @param seqno verified and authenticated seq number in host byte order + */ + void (*set_authenticated_seqno)(esp_context_t *this, + u_int32_t seqno); + + /** + * Destroy an esp_context_t + */ + void (*destroy)(esp_context_t *this); + +}; + +/** + * Create an esp_context_t instance + * + * @param enc_alg encryption algorithm + * @param enc_key encryption key + * @param int_alg integrity protection algorithm + * @param int_key integrity protection key + * @param inbound TRUE to create an inbound ESP context + * @return ESP context instance, or NULL if creation fails + */ +esp_context_t *esp_context_create(int enc_alg, chunk_t enc_key, int int_alg, + chunk_t int_key, bool inbound); + +#endif /** ESP_CONTEXT_H_ @}*/ + From 47eb8943b299900054beded5e21717989ff4bb8e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 5 Jul 2012 13:56:24 +0200 Subject: [PATCH 007/119] ESP packet wrapper added, handles encryption/decryption/verification etc. --- src/libipsec/Android.mk | 3 +- src/libipsec/Makefile.am | 3 +- src/libipsec/esp_packet.c | 402 ++++++++++++++++++++++++++++++++++++++ src/libipsec/esp_packet.h | 148 ++++++++++++++ 4 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 src/libipsec/esp_packet.c create mode 100644 src/libipsec/esp_packet.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index d452793c4..7292bff59 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -4,7 +4,8 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ ipsec.c ipsec.h \ -esp_context.c esp_context.h +esp_context.c esp_context.h \ +esp_packet.c esp_packet.h # build libipsec --------------------------------------------------------------- diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index df0c9acfa..b9ae6d336 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -2,7 +2,8 @@ ipseclib_LTLIBRARIES = libipsec.la libipsec_la_SOURCES = \ ipsec.c ipsec.h \ -esp_context.c esp_context.h +esp_context.c esp_context.h \ +esp_packet.c esp_packet.h libipsec_la_LIBADD = diff --git a/src/libipsec/esp_packet.c b/src/libipsec/esp_packet.c new file mode 100644 index 000000000..193bc621d --- /dev/null +++ b/src/libipsec/esp_packet.c @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#include "esp_packet.h" + +#include +#include +#include +#include +#include +#include + +#include + +typedef struct private_esp_packet_t private_esp_packet_t; + +/** + * Private additions to esp_packet_t. + */ +struct private_esp_packet_t { + + /** + * Public members + */ + esp_packet_t public; + + /** + * Source address + */ + host_t *src; + + /** + * Destination address + */ + host_t *dst; + + /** + * Payload of this packet + */ + chunk_t payload; + + /** + * Next Header info (e.g. IPPROTO_IPIP) + */ + u_int8_t next_header; + + /** + * Raw packet data + */ + chunk_t packet_data; +}; + +METHOD(esp_packet_t, parse_header, bool, + private_esp_packet_t *this, u_int32_t *spi) +{ + bio_reader_t *reader; + u_int32_t seq; + + reader = bio_reader_create(this->packet_data); + if (!reader->read_uint32(reader, spi) || + !reader->read_uint32(reader, &seq)) + { + DBG1(DBG_ESP, "failed to parse ESP header: invalid length"); + reader->destroy(reader); + return FALSE; + } + reader->destroy(reader); + + DBG2(DBG_ESP, "parsed ESP header with SPI %.8x [seq %u]", *spi, seq); + *spi = htonl(*spi); + return TRUE; +} + +/** + * Check padding as specified in RFC 4303 + */ +static bool check_padding(chunk_t padding) +{ + size_t i; + + for (i = 0; i < padding.len; ++i) + { + if (padding.ptr[i] != (u_int8_t)(i + 1)) + { + return FALSE; + } + } + return TRUE; +} + +/** + * Remove the padding from the payload and set the next header info + */ +static bool remove_padding(private_esp_packet_t *this) +{ + u_int8_t next_header, pad_length; + chunk_t padding; + bio_reader_t *reader; + + reader = bio_reader_create(this->payload); + if (!reader->read_uint8_end(reader, &next_header) || + !reader->read_uint8_end(reader, &pad_length)) + { + DBG1(DBG_ESP, "parsing ESP payload failed: invalid length"); + reader->destroy(reader); + return FALSE; + } + if (!reader->read_data_end(reader, pad_length, &padding) || + !check_padding(padding)) + { + DBG1(DBG_ESP, "parsing ESP payload failed: invalid padding"); + reader->destroy(reader); + return FALSE; + } + this->payload = reader->peek(reader); + this->next_header = next_header; + reader->destroy(reader); + + DBG3(DBG_ESP, "ESP payload:\n payload %B\n padding %B\n " + "padding length = %hhu, next header = %hhu", &this->payload, + &padding, pad_length, this->next_header); + return TRUE; +} + +METHOD(esp_packet_t, decrypt, status_t, + private_esp_packet_t *this, esp_context_t *esp_context) +{ + bio_reader_t *reader; + u_int32_t spi, seq; + chunk_t spi_seq, iv, icv, ciphertext; + crypter_t *crypter; + signer_t *signer; + + chunk_free(&this->payload); + + crypter = esp_context->get_crypter(esp_context); + signer = esp_context->get_signer(esp_context); + + reader = bio_reader_create(this->packet_data); + if (!reader->read_uint32(reader, &spi) || + !reader->read_uint32(reader, &seq) || + !reader->read_data(reader, crypter->get_iv_size(crypter), &iv) || + !reader->read_data_end(reader, signer->get_block_size(signer), &icv) || + reader->remaining(reader) % crypter->get_block_size(crypter)) + { + DBG1(DBG_ESP, "ESP decryption failed: invalid length"); + return PARSE_ERROR; + } + ciphertext = reader->peek(reader); + reader->destroy(reader); + + if (!esp_context->verify_seqno(esp_context, seq)) + { + DBG1(DBG_ESP, "ESP sequence number verification failed:\n " + "src %H, dst %H, SPI %.8x [seq %u]", + this->src, this->dst, spi, seq); + return VERIFY_ERROR; + } + DBG3(DBG_ESP, "ESP decryption:\n SPI %.8x [seq %u]\n IV %B\n " + "encrypted %B\n ICV %B", spi, seq, &iv, &ciphertext, &icv); + + spi_seq = chunk_create(this->packet_data.ptr, 8); + if (!signer->get_signature(signer, spi_seq, NULL) || + !signer->get_signature(signer, iv, NULL) || + !signer->verify_signature(signer, ciphertext, icv)) + { + DBG1(DBG_ESP, "ICV verification failed!"); + return FAILED; + } + esp_context->set_authenticated_seqno(esp_context, seq); + + if (!crypter->decrypt(crypter, ciphertext, iv, &this->payload)) + { + DBG1(DBG_ESP, "ESP decryption failed"); + return FAILED; + } + + if (!remove_padding(this)) + { + chunk_free(&this->payload); + return PARSE_ERROR; + } + return SUCCESS; +} + +/** + * Generate the padding as specified in RFC4303 + */ +static void generate_padding(chunk_t padding) +{ + size_t i; + + for (i = 0; i < padding.len; ++i) + { + padding.ptr[i] = (u_int8_t)(i + 1); + } +} + +METHOD(esp_packet_t, encrypt, status_t, + private_esp_packet_t *this, esp_context_t *esp_context, u_int32_t spi) +{ + chunk_t iv, icv, padding, ciphertext, auth_data; + bio_writer_t *writer; + u_int32_t next_seqno; + size_t blocksize, plainlen; + crypter_t *crypter; + signer_t *signer; + rng_t *rng; + + chunk_free(&this->packet_data); + + if (!esp_context->next_seqno(esp_context, &next_seqno)) + { + DBG1(DBG_ESP, "ESP encapsulation failed: sequence numbers cycled"); + return FAILED; + } + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + DBG1(DBG_ESP, "ESP encryption failed: could not find RNG"); + return NOT_FOUND; + } + crypter = esp_context->get_crypter(esp_context); + signer = esp_context->get_signer(esp_context); + + blocksize = crypter->get_block_size(crypter); + iv.len = crypter->get_iv_size(crypter); + icv.len = signer->get_block_size(signer); + + /* plaintext = payload, padding, pad_length, next_header */ + plainlen = this->payload.len + 2; + padding.len = blocksize - (plainlen % blocksize); + plainlen += padding.len; + + /* len = spi, seq, IV, plaintext, ICV */ + writer = bio_writer_create(2 * sizeof(u_int32_t) + iv.len + plainlen + + icv.len); + writer->write_uint32(writer, ntohl(spi)); + writer->write_uint32(writer, next_seqno); + + iv = writer->skip(writer, iv.len); + if (!rng->get_bytes(rng, iv.len, iv.ptr)) + { + DBG1(DBG_ESP, "ESP encryption failed: could not generate IV"); + writer->destroy(writer); + rng->destroy(rng); + return FAILED; + } + rng->destroy(rng); + + /* plain-/ciphertext will start here */ + ciphertext = writer->get_buf(writer); + ciphertext.ptr += ciphertext.len; + ciphertext.len = plainlen; + + writer->write_data(writer, this->payload); + + padding = writer->skip(writer, padding.len); + generate_padding(padding); + + writer->write_uint8(writer, padding.len); + writer->write_uint8(writer, this->next_header); + + DBG3(DBG_ESP, "ESP before encryption:\n payload = %B\n padding = %B\n " + "padding length = %hhu, next header = %hhu", &this->payload, &padding, + (u_int8_t)padding.len, this->next_header); + + /* encrypt the content inline */ + if (!crypter->encrypt(crypter, ciphertext, iv, NULL)) + { + DBG1(DBG_ESP, "ESP encryption failed"); + writer->destroy(writer); + return FAILED; + } + + /* calculate signature */ + auth_data = writer->get_buf(writer); + icv = writer->skip(writer, icv.len); + if (!signer->get_signature(signer, auth_data, icv.ptr)) + { + DBG1(DBG_ESP, "ESP encryption failed: signature generation failed"); + writer->destroy(writer); + return FAILED; + } + + DBG3(DBG_ESP, "ESP packet:\n SPI %.8x [seq %u]\n IV %B\n " + "encrypted %B\n ICV %B", ntohl(spi), next_seqno, &iv, + &ciphertext, &icv); + + this->packet_data = writer->extract_buf(writer); + writer->destroy(writer); + return SUCCESS; +} + +METHOD(esp_packet_t, get_next_header, u_int8_t, + private_esp_packet_t *this) +{ + return this->next_header; +} + +METHOD(esp_packet_t, get_payload, chunk_t, + private_esp_packet_t *this) +{ + return this->payload; +} + +METHOD(esp_packet_t, get_packet_data, chunk_t, + private_esp_packet_t *this) +{ + return this->packet_data; +} + +METHOD(esp_packet_t, get_source, host_t*, + private_esp_packet_t *this) +{ + return this->src; +} + +METHOD(esp_packet_t, get_destination, host_t*, + private_esp_packet_t *this) +{ + return this->dst; +} + +METHOD(esp_packet_t, destroy, void, + private_esp_packet_t *this) +{ + chunk_free(&this->payload); + chunk_free(&this->packet_data); + this->src->destroy(this->src); + this->dst->destroy(this->dst); + free(this); +} + +static private_esp_packet_t *esp_packet_create_empty(host_t *src, host_t *dst) +{ + private_esp_packet_t *this; + + INIT(this, + .public = { + .get_source = _get_source, + .get_destination = _get_destination, + .get_packet_data = _get_packet_data, + .get_payload = _get_payload, + .get_next_header = _get_next_header, + .parse_header = _parse_header, + .decrypt = _decrypt, + .encrypt = _encrypt, + .destroy = _destroy, + }, + .src = src, + .dst = dst, + .next_header = IPPROTO_NONE, + ); + return this; +} + +/** + * Described in header. + */ +esp_packet_t *esp_packet_create_from_packet(host_t *src, host_t *dst, + chunk_t packet_data) +{ + private_esp_packet_t *this; + + this = esp_packet_create_empty(src, dst); + this->packet_data = packet_data; + + return &this->public; +} + +/** + * Described in header. + */ +esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, + chunk_t payload, u_int8_t next_header) +{ + private_esp_packet_t *this; + + this = esp_packet_create_empty(src, dst); + this->next_header = next_header; + this->payload = payload; + + return &this->public; +} + diff --git a/src/libipsec/esp_packet.h b/src/libipsec/esp_packet.h new file mode 100644 index 000000000..473eeb4e5 --- /dev/null +++ b/src/libipsec/esp_packet.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup esp_packet esp_packet + * @{ @ingroup libipsec + */ + +#ifndef ESP_PACKET_H_ +#define ESP_PACKET_H_ + +#include "esp_context.h" + +#include +#include + +typedef struct esp_packet_t esp_packet_t; + +/** + * ESP packet + */ +struct esp_packet_t { + + /** + * Get the source address of this packet + * + * @return source host + */ + host_t *(*get_source)(esp_packet_t *this); + + /** + * Get the destination address of this packet + * + * @return destination host + */ + host_t *(*get_destination)(esp_packet_t *this); + + /** + * Parse the packet header before decryption. Tries to read the SPI + * from the packet to find a corresponding SA. + * + * @param spi parsed SPI, in network byte order + * @return TRUE when successful, FALSE otherwise (e.g. when the + * length of the packet is invalid) + */ + bool (*parse_header)(esp_packet_t *this, u_int32_t *spi); + + /** + * Authenticate and decrypt the packet. Also verifies the sequence number + * using the supplied ESP context and updates the anti-replay window. + * + * @param esp_context ESP context of corresponding inbound IPsec SA + * @return - SUCCESS if successfully authenticated, + * decrypted and parsed + * - PARSE_ERROR if the length of the packet or the + * padding is invalid + * - VERIFY_ERROR if the sequence number + * verification failed + * - FAILED if the ICV (MAC) check or the actual + * decryption failed + */ + status_t (*decrypt)(esp_packet_t *this, esp_context_t *esp_context); + + /** + * Encapsulate and encrypt the packet. The sequence number will be generated + * using the supplied ESP context. + * + * @param esp_context ESP context of corresponding outbound IPsec SA + * @param spi SPI value to use, in network byte order + * @return - SUCCESS if encrypted + * - FAILED if sequence number cycled or any of the + * cryptographic functions failed + * - NOT_FOUND if no suitable RNG could be found + */ + status_t (*encrypt)(esp_packet_t *this, esp_context_t *esp_context, + u_int32_t spi); + + /** + * Get the next header field of a packet. + * + * @note Packet has to be in the decrypted state. + * + * @return next header field + */ + u_int8_t (*get_next_header)(esp_packet_t *this); + + /** + * Get the plaintext payload of this packet (e.g. inner IP packet). + * + * @return plaintext payload (internal data), + * chunk_empty if not decrypted + */ + chunk_t (*get_payload)(esp_packet_t *this); + + /** + * Get the packet data to send / as received on the wire. + * + * @return encrypted packet data (internal data), + * chunk_empty if not encrypted + */ + chunk_t (*get_packet_data)(esp_packet_t *this); + + /** + * Destroy an esp_packet_t + */ + void (*destroy)(esp_packet_t *this); + +}; + +/** + * Create an ESP packet out of data from the wire. + * + * @param src source address from which the packet was sent, owned + * @param dst destination address to which the packet was sent, owned + * @param data the packet data as received, gets owned + * @return esp_packet_t instance + */ +esp_packet_t *esp_packet_create_from_packet(host_t *src, host_t *dst, + chunk_t data); + +/** + * Create an ESP packet from a plaintext payload (e.g. inner IP packet) + * + * @param src source address + * @param dst destination address + * @param payload plaintext payload (e.g. inner IP packet), gets owned + * @param next_header next header type of the payload (e.g IPPROTO_IPIP) + * @return esp_packet_t instance + */ +esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, + chunk_t payload, u_int8_t next_header); + +#endif /** ESP_PACKET_H_ @}*/ + From 3320b87a626dffd76333825bf8227febc4b7606f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 5 Jul 2012 15:46:54 +0200 Subject: [PATCH 008/119] Headers from libhydra (kernel interface related) are required in libipsec --- src/libipsec/Makefile.am | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index b9ae6d336..ce07e3cad 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -7,7 +7,9 @@ esp_packet.c esp_packet.h libipsec_la_LIBADD = -INCLUDES = -I$(top_srcdir)/src/libstrongswan +INCLUDES = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libhydra EXTRA_DIST = Android.mk From 5764a9b355843c91c390008447672b567bdf54de Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 6 Jul 2012 16:40:46 +0200 Subject: [PATCH 009/119] Moved packet_t to libstrongswan --- src/libcharon/Android.mk | 2 +- src/libcharon/Makefile.am | 2 +- src/libcharon/encoding/message.h | 2 +- src/libcharon/network/receiver.c | 2 +- src/libcharon/network/receiver.h | 2 +- src/libcharon/network/sender.h | 2 +- src/libcharon/network/socket.h | 2 +- src/libcharon/sa/ike_sa.h | 1 + src/libcharon/sa/ikev2/tasks/ike_mobike.h | 2 +- src/libstrongswan/Android.mk | 2 +- src/libstrongswan/Makefile.am | 7 ++- .../network => libstrongswan/utils}/packet.c | 11 ++-- .../network => libstrongswan/utils}/packet.h | 60 +++++++------------ 13 files changed, 43 insertions(+), 54 deletions(-) rename src/{libcharon/network => libstrongswan/utils}/packet.c (93%) rename src/{libcharon/network => libstrongswan/utils}/packet.h (55%) diff --git a/src/libcharon/Android.mk b/src/libcharon/Android.mk index 5e93e235f..a4ac87182 100644 --- a/src/libcharon/Android.mk +++ b/src/libcharon/Android.mk @@ -44,7 +44,7 @@ encoding/payloads/vendor_id_payload.c encoding/payloads/vendor_id_payload.h \ encoding/payloads/hash_payload.c encoding/payloads/hash_payload.h \ kernel/kernel_handler.c kernel/kernel_handler.h \ network/receiver.c network/receiver.h network/sender.c network/sender.h \ -network/packet.c network/packet.h network/socket.c network/socket.h \ +network/socket.c network/socket.h \ network/socket_manager.c network/socket_manager.h \ processing/jobs/acquire_job.c processing/jobs/acquire_job.h \ processing/jobs/delete_child_sa_job.c processing/jobs/delete_child_sa_job.h \ diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index df23e22f6..cd2c9de14 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -42,7 +42,7 @@ encoding/payloads/vendor_id_payload.c encoding/payloads/vendor_id_payload.h \ encoding/payloads/hash_payload.c encoding/payloads/hash_payload.h \ kernel/kernel_handler.c kernel/kernel_handler.h \ network/receiver.c network/receiver.h network/sender.c network/sender.h \ -network/packet.c network/packet.h network/socket.c network/socket.h \ +network/socket.c network/socket.h \ network/socket_manager.c network/socket_manager.h \ processing/jobs/acquire_job.c processing/jobs/acquire_job.h \ processing/jobs/delete_child_sa_job.c processing/jobs/delete_child_sa_job.h \ diff --git a/src/libcharon/encoding/message.h b/src/libcharon/encoding/message.h index 6f3c7967f..6d558daf6 100644 --- a/src/libcharon/encoding/message.h +++ b/src/libcharon/encoding/message.h @@ -27,11 +27,11 @@ typedef struct message_t message_t; #include -#include #include #include #include #include +#include #include /** diff --git a/src/libcharon/network/receiver.c b/src/libcharon/network/receiver.c index 3a52f8dc3..b270d65df 100644 --- a/src/libcharon/network/receiver.c +++ b/src/libcharon/network/receiver.c @@ -22,12 +22,12 @@ #include #include -#include #include #include #include #include #include +#include /** lifetime of a cookie, in seconds */ #define COOKIE_LIFETIME 10 diff --git a/src/libcharon/network/receiver.h b/src/libcharon/network/receiver.h index 93b3d3c0c..9e8edee45 100644 --- a/src/libcharon/network/receiver.h +++ b/src/libcharon/network/receiver.h @@ -26,8 +26,8 @@ typedef struct receiver_t receiver_t; #include -#include #include +#include /** * Callback called for any received UDP encapsulated ESP packet. diff --git a/src/libcharon/network/sender.h b/src/libcharon/network/sender.h index c4f18d73b..9b5c325cc 100644 --- a/src/libcharon/network/sender.h +++ b/src/libcharon/network/sender.h @@ -26,7 +26,7 @@ typedef struct sender_t sender_t; #include -#include +#include /** * Callback job responsible for sending IKE packets over the socket. diff --git a/src/libcharon/network/socket.h b/src/libcharon/network/socket.h index 4a4ef52e6..b8850c6ed 100644 --- a/src/libcharon/network/socket.h +++ b/src/libcharon/network/socket.h @@ -27,7 +27,7 @@ typedef struct socket_t socket_t; #include -#include +#include #include #include diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index e52355962..de9e0ede4 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -43,6 +43,7 @@ typedef struct ike_sa_t ike_sa_t; #include #include #include +#include /** * Timeout in seconds after that a half open IKE_SA gets deleted. diff --git a/src/libcharon/sa/ikev2/tasks/ike_mobike.h b/src/libcharon/sa/ikev2/tasks/ike_mobike.h index a7e3fe7e3..3b447af51 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_mobike.h +++ b/src/libcharon/sa/ikev2/tasks/ike_mobike.h @@ -26,7 +26,7 @@ typedef struct ike_mobike_t ike_mobike_t; #include #include #include -#include +#include /** * Task of type ike_mobike, detects and handles MOBIKE extension. diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 8cba58816..b6abf156f 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -24,7 +24,7 @@ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ -utils.c utils/host.c utils/identification.c utils/lexparser.c \ +utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ utils/linked_list.c utils/hashtable.c utils/enumerator.c utils/optionsfrom.c \ utils/capabilities.c utils/backtrace.c diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index dc849d6f2..b21f86f1e 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -22,7 +22,7 @@ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ -utils.c utils/host.c utils/identification.c utils/lexparser.c \ +utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ utils/linked_list.c utils/hashtable.c utils/enumerator.c utils/optionsfrom.c \ utils/capabilities.c utils/backtrace.c @@ -57,8 +57,9 @@ processing/jobs/callback_job.h processing/processor.h processing/scheduler.h \ selectors/traffic_selector.h threading/thread.h threading/thread_value.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ threading/rwlock.h threading/lock_profiler.h utils.h utils/host.h \ -utils/identification.h utils/lexparser.h utils/linked_list.h utils/hashtable.h \ -utils/enumerator.h utils/optionsfrom.h utils/capabilities.h utils/backtrace.h +utils/packet.h utils/identification.h utils/lexparser.h utils/linked_list.h \ +utils/hashtable.h utils/enumerator.h utils/optionsfrom.h utils/capabilities.h \ +utils/backtrace.h endif library.lo : $(top_builddir)/config.status diff --git a/src/libcharon/network/packet.c b/src/libstrongswan/utils/packet.c similarity index 93% rename from src/libcharon/network/packet.c rename to src/libstrongswan/utils/packet.c index c817e00fb..b5716fc4b 100644 --- a/src/libcharon/network/packet.c +++ b/src/libstrongswan/utils/packet.c @@ -110,15 +110,16 @@ METHOD(packet_t, clone_, packet_t*, packet_t *other; other = packet_create(); - if (this->destination != NULL) + if (this->destination) { - other->set_destination(other, this->destination->clone(this->destination)); + other->set_destination(other, + this->destination->clone(this->destination)); } - if (this->source != NULL) + if (this->source) { other->set_source(other, this->source->clone(this->source)); } - if (this->data.ptr != NULL) + if (this->data.ptr) { other->set_data(other, chunk_clone(this->adjusted_data)); } @@ -128,7 +129,7 @@ METHOD(packet_t, clone_, packet_t*, /* * Documented in header */ -packet_t *packet_create(void) +packet_t *packet_create() { private_packet_t *this; diff --git a/src/libcharon/network/packet.h b/src/libstrongswan/utils/packet.h similarity index 55% rename from src/libcharon/network/packet.h rename to src/libstrongswan/utils/packet.h index c53364104..9106bbef5 100644 --- a/src/libcharon/network/packet.h +++ b/src/libstrongswan/utils/packet.h @@ -17,7 +17,7 @@ /** * @defgroup packet packet - * @{ @ingroup network + * @{ @ingroup utils */ #ifndef PACKET_H_ @@ -29,97 +29,83 @@ typedef struct packet_t packet_t; #include /** - * Abstraction of an UDP-Packet, contains data, sender and receiver. + * Abstraction of an IP/UDP-Packet, contains data, sender and receiver. */ struct packet_t { /** * Set the source address. * - * Set host_t is now owned by packet_t, it will destroy - * it if necessary. - * - * @param source address to set as source + * @param source address to set as source (gets owned) */ - void (*set_source) (packet_t *packet, host_t *source); + void (*set_source)(packet_t *packet, host_t *source); /** * Set the destination address. * - * Set host_t is now owned by packet_t, it will destroy - * it if necessary. - * - * @param source address to set as destination + * @param source address to set as destination (gets owned) */ - void (*set_destination) (packet_t *packet, host_t *destination); + void (*set_destination)(packet_t *packet, host_t *destination); /** * Get the source address. * - * Set host_t is still owned by packet_t, clone it - * if needed. - * - * @return source address + * @return source address (internal data) */ - host_t *(*get_source) (packet_t *packet); + host_t *(*get_source)(packet_t *packet); /** * Get the destination address. * - * Set host_t is still owned by packet_t, clone it - * if needed. - * - * @return destination address + * @return destination address (internal data) */ - host_t *(*get_destination) (packet_t *packet); + host_t *(*get_destination)(packet_t *packet); /** * Get the data from the packet. * - * The data pointed by the chunk is still owned - * by the packet. Clone it if needed. - * - * @return chunk containing the data + * @return chunk containing the data (internal data) */ - chunk_t (*get_data) (packet_t *packet); + chunk_t (*get_data)(packet_t *packet); /** * Set the data in the packet. * - * Supplied chunk data is now owned by the - * packet. It will free it. - * - * @param data chunk with data to set + * @param data chunk with data to set (gets owned) */ - void (*set_data) (packet_t *packet, chunk_t data); + void (*set_data)(packet_t *packet, chunk_t data); /** * Increase the offset where the actual packet data starts. * + * The total offset applies to future calls of get_data() and clone(). + * * @note The offset is reset to 0 when set_data() is called. * * @param bytes the number of additional bytes to skip */ - void (*skip_bytes) (packet_t *packet, size_t bytes); + void (*skip_bytes)(packet_t *packet, size_t bytes); /** * Clones a packet_t object. * + * @note Data is cloned without skipped bytes. + * * @param clone clone of the packet */ - packet_t* (*clone) (packet_t *packet); + packet_t* (*clone)(packet_t *packet); /** * Destroy the packet, freeing contained data. */ - void (*destroy) (packet_t *packet); + void (*destroy)(packet_t *packet); }; /** - * create an empty packet + * Create an empty packet * * @return packet_t object */ -packet_t *packet_create(void); +packet_t *packet_create(); #endif /** PACKET_H_ @}*/ From ec486e9433320c425f6a31c7290ad7a806818ec2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 7 Jul 2012 12:46:28 +0200 Subject: [PATCH 010/119] Extended constructor for packet_t added (takes src, dst and data) --- src/libstrongswan/utils/packet.c | 17 ++++++++++++++--- src/libstrongswan/utils/packet.h | 10 ++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/libstrongswan/utils/packet.c b/src/libstrongswan/utils/packet.c index b5716fc4b..a2c329d60 100644 --- a/src/libstrongswan/utils/packet.c +++ b/src/libstrongswan/utils/packet.c @@ -126,10 +126,10 @@ METHOD(packet_t, clone_, packet_t*, return other; } -/* - * Documented in header +/** + * Described in header. */ -packet_t *packet_create() +packet_t *packet_create_from_data(host_t *src, host_t *dst, chunk_t data) { private_packet_t *this; @@ -145,8 +145,19 @@ packet_t *packet_create() .clone = _clone_, .destroy = _destroy, }, + .source = src, + .destination = dst, + .adjusted_data = data, + .data = data, ); return &this->public; } +/* + * Described in header. + */ +packet_t *packet_create() +{ + return packet_create_from_data(NULL, NULL, chunk_empty); +} diff --git a/src/libstrongswan/utils/packet.h b/src/libstrongswan/utils/packet.h index 9106bbef5..5c4440115 100644 --- a/src/libstrongswan/utils/packet.h +++ b/src/libstrongswan/utils/packet.h @@ -108,4 +108,14 @@ struct packet_t { */ packet_t *packet_create(); +/** + * Create a packet from the supplied data + * + * @param src source address (gets owned) + * @param dst destination address (gets owned) + * @param data packet data (gets owned) + * @return packet_t object + */ +packet_t *packet_create_from_data(host_t *src, host_t *dst, chunk_t data); + #endif /** PACKET_H_ @}*/ From 05a2a7950cb5ea440b41882da05f1eae280ba979 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 7 Jul 2012 13:31:07 +0200 Subject: [PATCH 011/119] esp_packet_t implements packet_t interface This should allow to avoid unnecessary cloning of packet data. --- src/libipsec/esp_packet.c | 142 ++++++++++++++++++++++++-------------- src/libipsec/esp_packet.h | 24 +++---- 2 files changed, 100 insertions(+), 66 deletions(-) diff --git a/src/libipsec/esp_packet.c b/src/libipsec/esp_packet.c index 193bc621d..75d8a7a94 100644 --- a/src/libipsec/esp_packet.c +++ b/src/libipsec/esp_packet.c @@ -40,14 +40,9 @@ struct private_esp_packet_t { esp_packet_t public; /** - * Source address + * Raw ESP packet */ - host_t *src; - - /** - * Destination address - */ - host_t *dst; + packet_t *packet; /** * Payload of this packet @@ -59,19 +54,73 @@ struct private_esp_packet_t { */ u_int8_t next_header; - /** - * Raw packet data - */ - chunk_t packet_data; }; +/** + * Forward declaration for clone() + */ +static private_esp_packet_t *esp_packet_create_empty(packet_t *packet); + +METHOD(packet_t, set_source, void, + private_esp_packet_t *this, host_t *src) +{ + return this->packet->set_source(this->packet, src); +} + +METHOD2(esp_packet_t, packet_t, get_source, host_t*, + private_esp_packet_t *this) +{ + return this->packet->get_source(this->packet); +} + +METHOD(packet_t, set_destination, void, + private_esp_packet_t *this, host_t *dst) +{ + return this->packet->set_destination(this->packet, dst); +} + +METHOD2(esp_packet_t, packet_t, get_destination, host_t*, + private_esp_packet_t *this) +{ + return this->packet->get_destination(this->packet); +} + +METHOD(packet_t, get_data, chunk_t, + private_esp_packet_t *this) +{ + return this->packet->get_data(this->packet); +} + +METHOD(packet_t, set_data, void, + private_esp_packet_t *this, chunk_t data) +{ + return this->packet->set_data(this->packet, data); +} + +METHOD(packet_t, skip_bytes, void, + private_esp_packet_t *this, size_t bytes) +{ + return this->packet->skip_bytes(this->packet, bytes); +} + +METHOD(packet_t, clone, packet_t*, + private_esp_packet_t *this) +{ + private_esp_packet_t *pkt; + + pkt = esp_packet_create_empty(this->packet->clone(this->packet)); + pkt->payload = chunk_clone(this->payload); + pkt->next_header = this->next_header; + return &pkt->public.packet; +} + METHOD(esp_packet_t, parse_header, bool, private_esp_packet_t *this, u_int32_t *spi) { bio_reader_t *reader; u_int32_t seq; - reader = bio_reader_create(this->packet_data); + reader = bio_reader_create(this->packet->get_data(this->packet)); if (!reader->read_uint32(reader, spi) || !reader->read_uint32(reader, &seq)) { @@ -142,16 +191,17 @@ METHOD(esp_packet_t, decrypt, status_t, { bio_reader_t *reader; u_int32_t spi, seq; - chunk_t spi_seq, iv, icv, ciphertext; + chunk_t data, iv, icv, ciphertext; crypter_t *crypter; signer_t *signer; chunk_free(&this->payload); + data = this->packet->get_data(this->packet); crypter = esp_context->get_crypter(esp_context); signer = esp_context->get_signer(esp_context); - reader = bio_reader_create(this->packet_data); + reader = bio_reader_create(data); if (!reader->read_uint32(reader, &spi) || !reader->read_uint32(reader, &seq) || !reader->read_data(reader, crypter->get_iv_size(crypter), &iv) || @@ -168,14 +218,13 @@ METHOD(esp_packet_t, decrypt, status_t, { DBG1(DBG_ESP, "ESP sequence number verification failed:\n " "src %H, dst %H, SPI %.8x [seq %u]", - this->src, this->dst, spi, seq); + get_source(this), get_destination(this), spi, seq); return VERIFY_ERROR; } DBG3(DBG_ESP, "ESP decryption:\n SPI %.8x [seq %u]\n IV %B\n " "encrypted %B\n ICV %B", spi, seq, &iv, &ciphertext, &icv); - spi_seq = chunk_create(this->packet_data.ptr, 8); - if (!signer->get_signature(signer, spi_seq, NULL) || + if (!signer->get_signature(signer, chunk_create(data.ptr, 8), NULL) || !signer->get_signature(signer, iv, NULL) || !signer->verify_signature(signer, ciphertext, icv)) { @@ -222,7 +271,7 @@ METHOD(esp_packet_t, encrypt, status_t, signer_t *signer; rng_t *rng; - chunk_free(&this->packet_data); + this->packet->set_data(this->packet, chunk_empty); if (!esp_context->next_seqno(esp_context, &next_seqno)) { @@ -303,7 +352,7 @@ METHOD(esp_packet_t, encrypt, status_t, "encrypted %B\n ICV %B", ntohl(spi), next_seqno, &iv, &ciphertext, &icv); - this->packet_data = writer->extract_buf(writer); + this->packet->set_data(this->packet, writer->extract_buf(writer)); writer->destroy(writer); return SUCCESS; } @@ -320,43 +369,33 @@ METHOD(esp_packet_t, get_payload, chunk_t, return this->payload; } -METHOD(esp_packet_t, get_packet_data, chunk_t, - private_esp_packet_t *this) -{ - return this->packet_data; -} - -METHOD(esp_packet_t, get_source, host_t*, - private_esp_packet_t *this) -{ - return this->src; -} - -METHOD(esp_packet_t, get_destination, host_t*, - private_esp_packet_t *this) -{ - return this->dst; -} - -METHOD(esp_packet_t, destroy, void, +METHOD2(esp_packet_t, packet_t, destroy, void, private_esp_packet_t *this) { chunk_free(&this->payload); - chunk_free(&this->packet_data); - this->src->destroy(this->src); - this->dst->destroy(this->dst); + this->packet->destroy(this->packet); free(this); } -static private_esp_packet_t *esp_packet_create_empty(host_t *src, host_t *dst) +static private_esp_packet_t *esp_packet_create_empty(packet_t *packet) { private_esp_packet_t *this; INIT(this, .public = { + .packet = { + .set_source = _set_source, + .get_source = _get_source, + .set_destination = _set_destination, + .get_destination = _get_destination, + .get_data = _get_data, + .set_data = _set_data, + .skip_bytes = _skip_bytes, + .clone = _clone, + .destroy = _destroy, + }, .get_source = _get_source, .get_destination = _get_destination, - .get_packet_data = _get_packet_data, .get_payload = _get_payload, .get_next_header = _get_next_header, .parse_header = _parse_header, @@ -364,8 +403,7 @@ static private_esp_packet_t *esp_packet_create_empty(host_t *src, host_t *dst) .encrypt = _encrypt, .destroy = _destroy, }, - .src = src, - .dst = dst, + .packet = packet, .next_header = IPPROTO_NONE, ); return this; @@ -374,13 +412,11 @@ static private_esp_packet_t *esp_packet_create_empty(host_t *src, host_t *dst) /** * Described in header. */ -esp_packet_t *esp_packet_create_from_packet(host_t *src, host_t *dst, - chunk_t packet_data) +esp_packet_t *esp_packet_create_from_packet(packet_t *packet) { private_esp_packet_t *this; - this = esp_packet_create_empty(src, dst); - this->packet_data = packet_data; + this = esp_packet_create_empty(packet); return &this->public; } @@ -389,14 +425,16 @@ esp_packet_t *esp_packet_create_from_packet(host_t *src, host_t *dst, * Described in header. */ esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, - chunk_t payload, u_int8_t next_header) + chunk_t payload, + u_int8_t next_header) { private_esp_packet_t *this; + packet_t *packet; - this = esp_packet_create_empty(src, dst); + packet = packet_create_from_data(src, dst, chunk_empty); + this = esp_packet_create_empty(packet); this->next_header = next_header; this->payload = payload; return &this->public; } - diff --git a/src/libipsec/esp_packet.h b/src/libipsec/esp_packet.h index 473eeb4e5..7dbbd1986 100644 --- a/src/libipsec/esp_packet.h +++ b/src/libipsec/esp_packet.h @@ -27,6 +27,7 @@ #include #include +#include typedef struct esp_packet_t esp_packet_t; @@ -35,6 +36,11 @@ typedef struct esp_packet_t esp_packet_t; */ struct esp_packet_t { + /** + * Implements packet_t interface to access the raw ESP packet + */ + packet_t packet; + /** * Get the source address of this packet * @@ -106,14 +112,6 @@ struct esp_packet_t { */ chunk_t (*get_payload)(esp_packet_t *this); - /** - * Get the packet data to send / as received on the wire. - * - * @return encrypted packet data (internal data), - * chunk_empty if not encrypted - */ - chunk_t (*get_packet_data)(esp_packet_t *this); - /** * Destroy an esp_packet_t */ @@ -124,13 +122,10 @@ struct esp_packet_t { /** * Create an ESP packet out of data from the wire. * - * @param src source address from which the packet was sent, owned - * @param dst destination address to which the packet was sent, owned - * @param data the packet data as received, gets owned + * @param packet the packet data as received, gets owned * @return esp_packet_t instance */ -esp_packet_t *esp_packet_create_from_packet(host_t *src, host_t *dst, - chunk_t data); +esp_packet_t *esp_packet_create_from_packet(packet_t *packet); /** * Create an ESP packet from a plaintext payload (e.g. inner IP packet) @@ -142,7 +137,8 @@ esp_packet_t *esp_packet_create_from_packet(host_t *src, host_t *dst, * @return esp_packet_t instance */ esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, - chunk_t payload, u_int8_t next_header); + chunk_t payload, + u_int8_t next_header); #endif /** ESP_PACKET_H_ @}*/ From 2dde79aca62315fcbbb64e7ec10d3d8dfaf91a59 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 10 Jul 2012 10:17:21 +0200 Subject: [PATCH 012/119] Added a simple blocking queue around linked_list_t --- src/libstrongswan/Android.mk | 4 +- src/libstrongswan/Makefile.am | 8 +- src/libstrongswan/utils/blocking_queue.c | 129 +++++++++++++++++++++++ src/libstrongswan/utils/blocking_queue.h | 97 +++++++++++++++++ 4 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 src/libstrongswan/utils/blocking_queue.c create mode 100644 src/libstrongswan/utils/blocking_queue.h diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index b6abf156f..398e371e8 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -25,8 +25,8 @@ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ -utils/linked_list.c utils/hashtable.c utils/enumerator.c utils/optionsfrom.c \ -utils/capabilities.c utils/backtrace.c +utils/linked_list.c utils/blocking_queue.c utils/hashtable.c utils/enumerator.c \ +utils/optionsfrom.c utils/capabilities.c utils/backtrace.c # adding the plugin source files diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index b21f86f1e..383efc8b8 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -23,8 +23,8 @@ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ -utils/linked_list.c utils/hashtable.c utils/enumerator.c utils/optionsfrom.c \ -utils/capabilities.c utils/backtrace.c +utils/linked_list.c utils/blocking_queue.c utils/hashtable.c utils/enumerator.c \ +utils/optionsfrom.c utils/capabilities.c utils/backtrace.c if USE_DEV_HEADERS strongswan_includedir = ${dev_headers} @@ -58,8 +58,8 @@ selectors/traffic_selector.h threading/thread.h threading/thread_value.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ threading/rwlock.h threading/lock_profiler.h utils.h utils/host.h \ utils/packet.h utils/identification.h utils/lexparser.h utils/linked_list.h \ -utils/hashtable.h utils/enumerator.h utils/optionsfrom.h utils/capabilities.h \ -utils/backtrace.h +utils/blocking_queue.h utils/hashtable.h utils/enumerator.h utils/optionsfrom.h \ +utils/capabilities.h utils/backtrace.h endif library.lo : $(top_builddir)/config.status diff --git a/src/libstrongswan/utils/blocking_queue.c b/src/libstrongswan/utils/blocking_queue.c new file mode 100644 index 000000000..c70184198 --- /dev/null +++ b/src/libstrongswan/utils/blocking_queue.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "blocking_queue.h" + +#include +#include +#include +#include + +typedef struct private_blocking_queue_t private_blocking_queue_t; + +/** + * Private data of a blocking_queue_t object. + */ +struct private_blocking_queue_t { + + /** + * Public part + */ + blocking_queue_t public; + + /** + * Linked list containing all items in the queue + */ + linked_list_t *list; + + /** + * Mutex used to synchronize access to the queue + */ + mutex_t *mutex; + + /** + * Condvar used to wait for items + */ + condvar_t *condvar; + +}; + +METHOD(blocking_queue_t, enqueue, void, + private_blocking_queue_t *this, void *item) +{ + this->mutex->lock(this->mutex); + this->list->insert_first(this->list, item); + this->condvar->signal(this->condvar); + this->mutex->unlock(this->mutex); +} + +METHOD(blocking_queue_t, dequeue, void*, + private_blocking_queue_t *this) +{ + bool oldstate; + void *item; + + + this->mutex->lock(this->mutex); + thread_cleanup_push((thread_cleanup_t)this->mutex->unlock, this->mutex); + /* ensure that a canceled thread does not dequeue any items */ + thread_cancellation_point(); + while (this->list->remove_last(this->list, &item) != SUCCESS) + { + oldstate = thread_cancelability(TRUE); + this->condvar->wait(this->condvar, this->mutex); + thread_cancelability(oldstate); + } + thread_cleanup_pop(TRUE); + return item; +} + +METHOD(blocking_queue_t, destroy, void, + private_blocking_queue_t *this) +{ + this->list->destroy(this->list); + this->condvar->destroy(this->condvar); + this->mutex->destroy(this->mutex); + free(this); +} + +METHOD(blocking_queue_t, destroy_offset, void, + private_blocking_queue_t *this, size_t offset) +{ + this->list->invoke_offset(this->list, offset); + destroy(this); +} + +METHOD(blocking_queue_t, destroy_function, void, + private_blocking_queue_t *this, void (*fn)(void*)) +{ + this->list->invoke_function(this->list, (linked_list_invoke_t)fn); + destroy(this); +} + +/* + * Described in header. + */ +blocking_queue_t *blocking_queue_create() +{ + private_blocking_queue_t *this; + + INIT(this, + .public = { + .enqueue = _enqueue, + .dequeue = _dequeue, + .destroy = _destroy, + .destroy_offset = _destroy_offset, + .destroy_function = _destroy_function, + }, + .list = linked_list_create(), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), + ); + + return &this->public; +} + diff --git a/src/libstrongswan/utils/blocking_queue.h b/src/libstrongswan/utils/blocking_queue.h new file mode 100644 index 000000000..cf2712cf4 --- /dev/null +++ b/src/libstrongswan/utils/blocking_queue.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup blocking_queue blocking_queue + * @{ @ingroup utils + */ + +#ifndef BLOCKING_QUEUE_H_ +#define BLOCKING_QUEUE_H_ + +typedef struct blocking_queue_t blocking_queue_t; + +#include + +/** + * Class implementing a synchronized blocking queue based on linked_list_t + */ +struct blocking_queue_t { + + /** + * Inserts a new item at the tail of the queue + * + * @param item item to insert in queue + */ + void (*enqueue)(blocking_queue_t *this, void *item); + + /** + * Removes the first item in the queue and returns its value. + * If the queue is empty, this call blocks until a new item is inserted. + * + * @note This is a thread cancellation point + * + * @return removed item + */ + void *(*dequeue)(blocking_queue_t *this); + + /** + * Destroys a blocking_queue_t object. + * + * @note No thread must wait in dequeue() when this function is called + */ + void (*destroy)(blocking_queue_t *this); + + /** + * Destroys a queue and its objects using the given destructor. + * + * If a queue and the contained objects should be destroyed, use + * destroy_offset. The supplied offset specifies the destructor to + * call on each object. The offset may be calculated using the offsetof + * macro, e.g.: queue->destroy_offset(queue, offsetof(object_t, destroy)); + * + * @note No thread must wait in dequeue() when this function is called + * + * @param offset offset of the objects destructor + */ + void (*destroy_offset)(blocking_queue_t *this, size_t offset); + + /** + * Destroys a queue and its objects using a cleanup function. + * + * If a queue and its contents should get destroyed using a specific + * cleanup function, use destroy_function. This is useful when the + * list contains malloc()-ed blocks which should get freed, + * e.g.: queue->destroy_function(queue, free); + * + * @note No thread must wait in dequeue() when this function is called + * + * @param function function to call on each object + */ + void (*destroy_function)(blocking_queue_t *this, void (*)(void*)); + +}; + +/** + * Creates an empty queue object. + * + * @return blocking_queue_t object. + */ +blocking_queue_t *blocking_queue_create(); + +#endif /** BLOCKING_QUEUE_H_ @}*/ + From 053276e69a182c33dcbac2d7b510d2734cd0bfd3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 12 Jul 2012 16:56:35 +0200 Subject: [PATCH 013/119] Use a CALLBACK feature to create charon's sender and receiver --- src/libcharon/daemon.c | 43 ++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/libcharon/daemon.c b/src/libcharon/daemon.c index 612796a78..6e977efc4 100644 --- a/src/libcharon/daemon.c +++ b/src/libcharon/daemon.c @@ -102,7 +102,6 @@ static void destroy(private_daemon_t *this) /* cancel all threads and wait for their termination */ lib->processor->cancel(lib->processor); - DESTROY_IF(this->public.receiver); #ifdef ME DESTROY_IF(this->public.connect_manager); DESTROY_IF(this->public.mediation_manager); @@ -118,7 +117,6 @@ static void destroy(private_daemon_t *this) DESTROY_IF(this->public.eap); DESTROY_IF(this->public.xauth); DESTROY_IF(this->public.backends); - DESTROY_IF(this->public.sender); DESTROY_IF(this->public.socket); DESTROY_IF(this->public.caps); @@ -142,17 +140,44 @@ METHOD(daemon_t, start, void, DEFAULT_THREADS, charon->name)); } + +/** + * Initialize/deinitialize sender and receiver + */ +static bool sender_receiver_cb(void *plugin, plugin_feature_t *feature, + bool reg, private_daemon_t *this) +{ + if (reg) + { + this->public.receiver = receiver_create(); + if (!this->public.receiver) + { + return FALSE; + } + this->public.sender = sender_create(); + } + else + { + DESTROY_IF(this->public.receiver); + DESTROY_IF(this->public.sender); + } + return TRUE; +} + METHOD(daemon_t, initialize, bool, private_daemon_t *this, char *plugins) { - static plugin_feature_t features[] = { + plugin_feature_t features[] = { PLUGIN_PROVIDE(CUSTOM, "libcharon"), - PLUGIN_DEPENDS(HASHER, HASH_SHA1), - PLUGIN_DEPENDS(RNG, RNG_STRONG), PLUGIN_DEPENDS(NONCE_GEN), + PLUGIN_DEPENDS(CUSTOM, "libcharon-receiver"), PLUGIN_DEPENDS(CUSTOM, "kernel-ipsec"), PLUGIN_DEPENDS(CUSTOM, "kernel-net"), - PLUGIN_DEPENDS(CUSTOM, "socket"), + PLUGIN_CALLBACK((plugin_feature_callback_t)sender_receiver_cb, this), + PLUGIN_PROVIDE(CUSTOM, "libcharon-receiver"), + PLUGIN_DEPENDS(HASHER, HASH_SHA1), + PLUGIN_DEPENDS(RNG, RNG_STRONG), + PLUGIN_DEPENDS(CUSTOM, "socket"), }; lib->plugins->add_static_features(lib->plugins, charon->name, features, countof(features), TRUE); @@ -170,12 +195,6 @@ METHOD(daemon_t, initialize, bool, { return FALSE; } - this->public.sender = sender_create(); - this->public.receiver = receiver_create(); - if (this->public.receiver == NULL) - { - return FALSE; - } /* Queue start_action job */ lib->processor->queue_job(lib->processor, (job_t*)start_action_job_create()); From 156f7e9b857db46b092ebddf7c5c5baf770f92b3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 11:02:08 +0200 Subject: [PATCH 014/119] Moved types used by kernel_ipsec_t interface (and libipsec) to libstrongswan This avoids a dependency of libipsec to libhydra. --- src/libhydra/kernel/kernel_ipsec.c | 22 ---- src/libhydra/kernel/kernel_ipsec.h | 147 +--------------------- src/libipsec/Android.mk | 3 +- src/libipsec/Makefile.am | 3 +- src/libstrongswan/Android.mk | 1 + src/libstrongswan/Makefile.am | 10 +- src/libstrongswan/ipsec/ipsec_types.c | 38 ++++++ src/libstrongswan/ipsec/ipsec_types.h | 172 ++++++++++++++++++++++++++ src/libstrongswan/library.h | 3 + 9 files changed, 223 insertions(+), 176 deletions(-) create mode 100644 src/libstrongswan/ipsec/ipsec_types.c create mode 100644 src/libstrongswan/ipsec/ipsec_types.h diff --git a/src/libhydra/kernel/kernel_ipsec.c b/src/libhydra/kernel/kernel_ipsec.c index 9b38297cc..1a32ab4e7 100644 --- a/src/libhydra/kernel/kernel_ipsec.c +++ b/src/libhydra/kernel/kernel_ipsec.c @@ -17,28 +17,6 @@ #include -ENUM(ipsec_mode_names, MODE_TRANSPORT, MODE_DROP, - "TRANSPORT", - "TUNNEL", - "BEET", - "PASS", - "DROP" -); - -ENUM(policy_dir_names, POLICY_IN, POLICY_FWD, - "in", - "out", - "fwd" -); - -ENUM(ipcomp_transform_names, IPCOMP_NONE, IPCOMP_LZJH, - "IPCOMP_NONE", - "IPCOMP_OUI", - "IPCOMP_DEFLATE", - "IPCOMP_LZS", - "IPCOMP_LZJH" -); - /** * See header */ diff --git a/src/libhydra/kernel/kernel_ipsec.h b/src/libhydra/kernel/kernel_ipsec.h index 500a77cad..ee0ade2aa 100644 --- a/src/libhydra/kernel/kernel_ipsec.h +++ b/src/libhydra/kernel/kernel_ipsec.h @@ -24,158 +24,13 @@ #ifndef KERNEL_IPSEC_H_ #define KERNEL_IPSEC_H_ -typedef enum ipsec_mode_t ipsec_mode_t; -typedef enum policy_dir_t policy_dir_t; -typedef enum policy_type_t policy_type_t; -typedef enum policy_priority_t policy_priority_t; -typedef enum ipcomp_transform_t ipcomp_transform_t; typedef struct kernel_ipsec_t kernel_ipsec_t; -typedef struct ipsec_sa_cfg_t ipsec_sa_cfg_t; -typedef struct lifetime_cfg_t lifetime_cfg_t; -typedef struct mark_t mark_t; #include -#include +#include #include #include -/** - * Mode of an IPsec SA. - */ -enum ipsec_mode_t { - /** not using any encapsulation */ - MODE_NONE = 0, - /** transport mode, no inner address */ - MODE_TRANSPORT = 1, - /** tunnel mode, inner and outer addresses */ - MODE_TUNNEL, - /** BEET mode, tunnel mode but fixed, bound inner addresses */ - MODE_BEET, - /** passthrough policy for traffic without an IPsec SA */ - MODE_PASS, - /** drop policy discarding traffic */ - MODE_DROP -}; - -/** - * enum names for ipsec_mode_t. - */ -extern enum_name_t *ipsec_mode_names; - -/** - * Direction of a policy. These are equal to those - * defined in xfrm.h, but we want to stay implementation - * neutral here. - */ -enum policy_dir_t { - /** Policy for inbound traffic */ - POLICY_IN = 0, - /** Policy for outbound traffic */ - POLICY_OUT = 1, - /** Policy for forwarded traffic */ - POLICY_FWD = 2, -}; - -/** - * enum names for policy_dir_t. - */ -extern enum_name_t *policy_dir_names; - -/** - * Type of a policy. - */ -enum policy_type_t { - /** Normal IPsec policy */ - POLICY_IPSEC = 1, - /** Passthrough policy (traffic is ignored by IPsec) */ - POLICY_PASS, - /** Drop policy (traffic is discarded) */ - POLICY_DROP, -}; - -/** - * High-level priority of a policy. - */ -enum policy_priority_t { - /** Default priority */ - POLICY_PRIORITY_DEFAULT, - /** Priority for trap policies */ - POLICY_PRIORITY_ROUTED, - /** Priority for fallback drop policies */ - POLICY_PRIORITY_FALLBACK, -}; - -/** - * IPComp transform IDs, as in RFC 4306 - */ -enum ipcomp_transform_t { - IPCOMP_NONE = 0, - IPCOMP_OUI = 1, - IPCOMP_DEFLATE = 2, - IPCOMP_LZS = 3, - IPCOMP_LZJH = 4, -}; - -/** - * enum strings for ipcomp_transform_t. - */ -extern enum_name_t *ipcomp_transform_names; - -/** - * This struct contains details about IPsec SA(s) tied to a policy. - */ -struct ipsec_sa_cfg_t { - /** mode of SA (tunnel, transport) */ - ipsec_mode_t mode; - /** unique ID */ - u_int32_t reqid; - /** details about ESP/AH */ - struct { - /** TRUE if this protocol is used */ - bool use; - /** SPI for ESP/AH */ - u_int32_t spi; - } esp, ah; - /** details about IPComp */ - struct { - /** the IPComp transform used */ - u_int16_t transform; - /** CPI for IPComp */ - u_int16_t cpi; - } ipcomp; -}; - -/** - * A lifetime_cfg_t defines the lifetime limits of an SA. - * - * Set any of these values to 0 to ignore. - */ -struct lifetime_cfg_t { - struct { - /** Limit before the SA gets invalid. */ - u_int64_t life; - /** Limit before the SA gets rekeyed. */ - u_int64_t rekey; - /** The range of a random value subtracted from rekey. */ - u_int64_t jitter; - } time, bytes, packets; -}; - -/** - * A mark_t defines an optional mark in an IPsec SA. - */ -struct mark_t { - /** Mark value */ - u_int32_t value; - /** Mark mask */ - u_int32_t mask; -}; - -/** - * Special mark value that uses the reqid of the CHILD_SA as mark - */ -#define MARK_REQID (0xFFFFFFFF) - /** * Interface to the ipsec subsystem of the kernel. * diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 7292bff59..c4cf92d39 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -12,7 +12,6 @@ esp_packet.c esp_packet.h LOCAL_C_INCLUDES += \ $(libvstr_PATH) \ $(strongswan_PATH)/src/include \ - $(strongswan_PATH)/src/libhydra \ $(strongswan_PATH)/src/libstrongswan LOCAL_CFLAGS := $(strongswan_CFLAGS) @@ -25,7 +24,7 @@ LOCAL_ARM_MODE := arm LOCAL_PRELINK_MODULE := false -LOCAL_SHARED_LIBRARIES += libstrongswan libhydra +LOCAL_SHARED_LIBRARIES += libstrongswan include $(BUILD_SHARED_LIBRARY) diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index ce07e3cad..128de7a1f 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -8,8 +8,7 @@ esp_packet.c esp_packet.h libipsec_la_LIBADD = INCLUDES = \ - -I$(top_srcdir)/src/libstrongswan \ - -I$(top_srcdir)/src/libhydra + -I$(top_srcdir)/src/libstrongswan EXTRA_DIST = Android.mk diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 398e371e8..389120e73 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -20,6 +20,7 @@ credentials/sets/auth_cfg_wrapper.c credentials/sets/ocsp_response_wrapper.c \ credentials/sets/cert_cache.c credentials/sets/mem_cred.c \ credentials/sets/callback_cred.c credentials/auth_cfg.c database/database.c \ database/database_factory.c fetcher/fetcher.c fetcher/fetcher_manager.c eap/eap.c \ +ipsec/ipsec_types.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 383efc8b8..1f27f01ec 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -18,6 +18,7 @@ credentials/sets/auth_cfg_wrapper.c credentials/sets/ocsp_response_wrapper.c \ credentials/sets/cert_cache.c credentials/sets/mem_cred.c \ credentials/sets/callback_cred.c credentials/auth_cfg.c database/database.c \ database/database_factory.c fetcher/fetcher.c fetcher/fetcher_manager.c eap/eap.c \ +ipsec/ipsec_types.c \ pen/pen.c plugins/plugin_loader.c plugins/plugin_feature.c processing/jobs/job.c \ processing/jobs/callback_job.c processing/processor.c processing/scheduler.c \ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ @@ -51,10 +52,11 @@ credentials/sets/ocsp_response_wrapper.h credentials/sets/cert_cache.h \ credentials/sets/mem_cred.h credentials/sets/callback_cred.h \ credentials/auth_cfg.h credentials/credential_set.h credentials/cert_validator.h \ database/database.h database/database_factory.h fetcher/fetcher.h \ -fetcher/fetcher_manager.h eap/eap.h pen/pen.h plugins/plugin_loader.h \ -plugins/plugin.h plugins/plugin_feature.h processing/jobs/job.h \ -processing/jobs/callback_job.h processing/processor.h processing/scheduler.h \ -selectors/traffic_selector.h threading/thread.h threading/thread_value.h \ +fetcher/fetcher_manager.h eap/eap.h pen/pen.h ipsec/ipsec_types.h \ +plugins/plugin_loader.h plugins/plugin.h plugins/plugin_feature.h +processing/jobs/job.h processing/jobs/callback_job.h processing/processor.h +processing/scheduler.h selectors/traffic_selector.h \ +threading/thread.h threading/thread_value.h \ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h \ threading/rwlock.h threading/lock_profiler.h utils.h utils/host.h \ utils/packet.h utils/identification.h utils/lexparser.h utils/linked_list.h \ diff --git a/src/libstrongswan/ipsec/ipsec_types.c b/src/libstrongswan/ipsec/ipsec_types.c new file mode 100644 index 000000000..e4e927313 --- /dev/null +++ b/src/libstrongswan/ipsec/ipsec_types.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec_types.h" + +ENUM(ipsec_mode_names, MODE_TRANSPORT, MODE_DROP, + "TRANSPORT", + "TUNNEL", + "BEET", + "PASS", + "DROP" +); + +ENUM(policy_dir_names, POLICY_IN, POLICY_FWD, + "in", + "out", + "fwd" +); + +ENUM(ipcomp_transform_names, IPCOMP_NONE, IPCOMP_LZJH, + "IPCOMP_NONE", + "IPCOMP_OUI", + "IPCOMP_DEFLATE", + "IPCOMP_LZS", + "IPCOMP_LZJH" +); diff --git a/src/libstrongswan/ipsec/ipsec_types.h b/src/libstrongswan/ipsec/ipsec_types.h new file mode 100644 index 000000000..32e55bc50 --- /dev/null +++ b/src/libstrongswan/ipsec/ipsec_types.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_types ipsec_types + * @{ @ingroup ipsec + */ + +#ifndef IPSEC_TYPES_H_ +#define IPSEC_TYPES_H_ + +typedef enum ipsec_mode_t ipsec_mode_t; +typedef enum policy_dir_t policy_dir_t; +typedef enum policy_type_t policy_type_t; +typedef enum policy_priority_t policy_priority_t; +typedef enum ipcomp_transform_t ipcomp_transform_t; +typedef struct ipsec_sa_cfg_t ipsec_sa_cfg_t; +typedef struct lifetime_cfg_t lifetime_cfg_t; +typedef struct mark_t mark_t; + +#include + +/** + * Mode of an IPsec SA. + */ +enum ipsec_mode_t { + /** not using any encapsulation */ + MODE_NONE = 0, + /** transport mode, no inner address */ + MODE_TRANSPORT = 1, + /** tunnel mode, inner and outer addresses */ + MODE_TUNNEL, + /** BEET mode, tunnel mode but fixed, bound inner addresses */ + MODE_BEET, + /** passthrough policy for traffic without an IPsec SA */ + MODE_PASS, + /** drop policy discarding traffic */ + MODE_DROP +}; + +/** + * enum names for ipsec_mode_t. + */ +extern enum_name_t *ipsec_mode_names; + +/** + * Direction of a policy. These are equal to those + * defined in xfrm.h, but we want to stay implementation + * neutral here. + */ +enum policy_dir_t { + /** Policy for inbound traffic */ + POLICY_IN = 0, + /** Policy for outbound traffic */ + POLICY_OUT = 1, + /** Policy for forwarded traffic */ + POLICY_FWD = 2, +}; + +/** + * enum names for policy_dir_t. + */ +extern enum_name_t *policy_dir_names; + +/** + * Type of a policy. + */ +enum policy_type_t { + /** Normal IPsec policy */ + POLICY_IPSEC = 1, + /** Passthrough policy (traffic is ignored by IPsec) */ + POLICY_PASS, + /** Drop policy (traffic is discarded) */ + POLICY_DROP, +}; + +/** + * High-level priority of a policy. + */ +enum policy_priority_t { + /** Default priority */ + POLICY_PRIORITY_DEFAULT, + /** Priority for trap policies */ + POLICY_PRIORITY_ROUTED, + /** Priority for fallback drop policies */ + POLICY_PRIORITY_FALLBACK, +}; + +/** + * IPComp transform IDs, as in RFC 4306 + */ +enum ipcomp_transform_t { + IPCOMP_NONE = 0, + IPCOMP_OUI = 1, + IPCOMP_DEFLATE = 2, + IPCOMP_LZS = 3, + IPCOMP_LZJH = 4, +}; + +/** + * enum strings for ipcomp_transform_t. + */ +extern enum_name_t *ipcomp_transform_names; + +/** + * This struct contains details about IPsec SA(s) tied to a policy. + */ +struct ipsec_sa_cfg_t { + /** mode of SA (tunnel, transport) */ + ipsec_mode_t mode; + /** unique ID */ + u_int32_t reqid; + /** details about ESP/AH */ + struct { + /** TRUE if this protocol is used */ + bool use; + /** SPI for ESP/AH */ + u_int32_t spi; + } esp, ah; + /** details about IPComp */ + struct { + /** the IPComp transform used */ + u_int16_t transform; + /** CPI for IPComp */ + u_int16_t cpi; + } ipcomp; +}; + +/** + * A lifetime_cfg_t defines the lifetime limits of an SA. + * + * Set any of these values to 0 to ignore. + */ +struct lifetime_cfg_t { + struct { + /** Limit before the SA gets invalid. */ + u_int64_t life; + /** Limit before the SA gets rekeyed. */ + u_int64_t rekey; + /** The range of a random value subtracted from rekey. */ + u_int64_t jitter; + } time, bytes, packets; +}; + +/** + * A mark_t defines an optional mark in an IPsec SA. + */ +struct mark_t { + /** Mark value */ + u_int32_t value; + /** Mark mask */ + u_int32_t mask; +}; + +/** + * Special mark value that uses the reqid of the CHILD_SA as mark + */ +#define MARK_REQID (0xFFFFFFFF) + +#endif /** IPSEC_TYPES_H_ @}*/ diff --git a/src/libstrongswan/library.h b/src/libstrongswan/library.h index d357ddf5a..634128fe9 100644 --- a/src/libstrongswan/library.h +++ b/src/libstrongswan/library.h @@ -43,6 +43,9 @@ * @defgroup fetcher fetcher * @ingroup libstrongswan * + * @defgroup ipsec ipsec + * @ingroup libstrongswan + * * @defgroup plugins plugins * @ingroup libstrongswan * From f9b0c0547500d9a0f767a95b1ef821a3742d0ee4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 11:06:35 +0200 Subject: [PATCH 015/119] Class representing an IPsec SA added The IPsec SA also manages the respective ESP context. --- src/libipsec/Android.mk | 3 +- src/libipsec/Makefile.am | 3 +- src/libipsec/ipsec_sa.c | 212 +++++++++++++++++++++++++++++++++++++++ src/libipsec/ipsec_sa.h | 140 ++++++++++++++++++++++++++ 4 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 src/libipsec/ipsec_sa.c create mode 100644 src/libipsec/ipsec_sa.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index c4cf92d39..2c1c1cc93 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -5,7 +5,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ -esp_packet.c esp_packet.h +esp_packet.c esp_packet.h \ +ipsec_sa.c ipsec_sa.h # build libipsec --------------------------------------------------------------- diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 128de7a1f..655849632 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -3,7 +3,8 @@ ipseclib_LTLIBRARIES = libipsec.la libipsec_la_SOURCES = \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ -esp_packet.c esp_packet.h +esp_packet.c esp_packet.h \ +ipsec_sa.c ipsec_sa.h libipsec_la_LIBADD = diff --git a/src/libipsec/ipsec_sa.c b/src/libipsec/ipsec_sa.c new file mode 100644 index 000000000..02fa81354 --- /dev/null +++ b/src/libipsec/ipsec_sa.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec_sa.h" + +#include +#include + +typedef struct private_ipsec_sa_t private_ipsec_sa_t; + +/** + * Private additions to ipsec_sa_t. + */ +struct private_ipsec_sa_t { + + /** + * Public members + */ + ipsec_sa_t public; + + /** + * SPI of this SA + */ + u_int32_t spi; + + /** + * Source address + */ + host_t *src; + + /** + * Destination address + */ + host_t *dst; + + /** + * Protocol + */ + u_int8_t protocol; + + /** + * Reqid of this SA + */ + u_int32_t reqid; + + /** + * Lifetime configuration + */ + lifetime_cfg_t lifetime; + + /** + * IPsec mode + */ + ipsec_mode_t mode; + + /** + * TRUE if extended sequence numbers are used + */ + bool esn; + + /** + * TRUE if this is an inbound SA + */ + bool inbound; + + /** + * ESP context + */ + esp_context_t *esp_context; +}; + +METHOD(ipsec_sa_t, get_source, host_t*, + private_ipsec_sa_t *this) +{ + return this->src; +} + +METHOD(ipsec_sa_t, get_destination, host_t*, + private_ipsec_sa_t *this) +{ + return this->dst; +} + +METHOD(ipsec_sa_t, get_spi, u_int32_t, + private_ipsec_sa_t *this) +{ + return this->spi; +} + +METHOD(ipsec_sa_t, get_reqid, u_int32_t, + private_ipsec_sa_t *this) +{ + return this->reqid; +} + +METHOD(ipsec_sa_t, get_protocol, u_int8_t, + private_ipsec_sa_t *this) +{ + return this->protocol; +} + +METHOD(ipsec_sa_t, get_lifetime, lifetime_cfg_t*, + private_ipsec_sa_t *this) +{ + return &this->lifetime; +} + +METHOD(ipsec_sa_t, is_inbound, bool, + private_ipsec_sa_t *this) +{ + return this->inbound; +} + +METHOD(ipsec_sa_t, get_esp_context, esp_context_t*, + private_ipsec_sa_t *this) +{ + return this->esp_context; +} + +METHOD(ipsec_sa_t, destroy, void, + private_ipsec_sa_t *this) +{ + this->src->destroy(this->src); + this->dst->destroy(this->dst); + DESTROY_IF(this->esp_context); + free(this); +} + +/** + * Described in header. + */ +ipsec_sa_t *ipsec_sa_create(u_int32_t spi, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t reqid, mark_t mark, u_int32_t tfc, + lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key, + u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, + u_int16_t ipcomp, u_int16_t cpi, bool encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts) +{ + private_ipsec_sa_t *this; + + if (protocol != IPPROTO_ESP) + { + DBG1(DBG_ESP, " IPsec SA: protocol not supported"); + return NULL; + } + if (!encap) + { + DBG1(DBG_ESP, " IPsec SA: only UDP encapsulation is supported"); + return NULL; + } + if (esn) + { + DBG1(DBG_ESP, " IPsec SA: ESN not supported"); + return NULL; + } + if (ipcomp != IPCOMP_NONE) + { + DBG1(DBG_ESP, " IPsec SA: compression not supported"); + return NULL; + } + if (mode != MODE_TUNNEL) + { + DBG1(DBG_ESP, " IPsec SA: unsupported mode"); + return NULL; + } + + INIT(this, + .public = { + .destroy = _destroy, + .get_source = _get_source, + .get_destination = _get_destination, + .get_spi = _get_spi, + .get_reqid = _get_reqid, + .get_protocol = _get_protocol, + .get_lifetime = _get_lifetime, + .is_inbound = _is_inbound, + .get_esp_context = _get_esp_context, + }, + .spi = spi, + .src = src->clone(src), + .dst = dst->clone(dst), + .lifetime = *lifetime, + .protocol = protocol, + .reqid = reqid, + .mode = mode, + .esn = esn, + .inbound = inbound, + ); + + this->esp_context = esp_context_create(enc_alg, enc_key, int_alg, int_key, + inbound); + if (!this->esp_context) + { + destroy(this); + return NULL; + } + return &this->public; +} diff --git a/src/libipsec/ipsec_sa.h b/src/libipsec/ipsec_sa.h new file mode 100644 index 000000000..5cf559a38 --- /dev/null +++ b/src/libipsec/ipsec_sa.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_sa ipsec_sa + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_SA_H_ +#define IPSEC_SA_H_ + +#include "esp_context.h" + +#include +#include +#include +#include + +typedef struct ipsec_sa_t ipsec_sa_t; + +/** + * IPsec Security Association (SA) + */ +struct ipsec_sa_t { + + /** + * Get the source address for this SA + * + * @return source address of this SA + */ + host_t *(*get_source)(ipsec_sa_t *this); + + /** + * Get the destination address for this SA + * + * @return destination address of this SA + */ + host_t *(*get_destination)(ipsec_sa_t *this); + + /** + * Get the SPI for this SA + * + * @return SPI of this SA + */ + u_int32_t (*get_spi)(ipsec_sa_t *this); + + /** + * Get the reqid of this SA + * + * @return reqid of this SA + */ + u_int32_t (*get_reqid)(ipsec_sa_t *this); + + /** + * Get the protocol (e.g. IPPROTO_ESP) of this SA + * + * @return protocol of this SA + */ + u_int8_t (*get_protocol)(ipsec_sa_t *this); + + /** + * Returns whether this SA is inbound or outbound + * + * @return TRUE if inbound, FALSE if outbound + */ + bool (*is_inbound)(ipsec_sa_t *this); + + /** + * Get the lifetime information for this SA + * Note that this information is always relative to the time when the + * SA was installed (i.e. it is not adjusted over time) + * + * @return lifetime of this SA + */ + lifetime_cfg_t *(*get_lifetime)(ipsec_sa_t *this); + + /** + * Get the ESP context for this SA + * + * @return ESP context of this SA + */ + esp_context_t *(*get_esp_context)(ipsec_sa_t *this); + + /** + * Destroy an ipsec_sa_t + */ + void (*destroy)(ipsec_sa_t *this); + +}; + +/** + * Create an ipsec_sa_t instance + * + * @param spi SPI for this SA + * @param src source address for this SA (gets cloned) + * @param dst destination address for this SA (gets cloned) + * @param protocol protocol for this SA (only ESP is supported) + * @param reqid reqid for this SA + * @param mark mark for this SA (ignored) + * @param tfc Traffic Flow Confidentiality (currently not supported) + * @param lifetime lifetime for this SA + * @param enc_alg encryption algorithm for this SA + * @param enc_key encryption key for this SA + * @param int_alg integrity protection algorithm + * @param int_key integrity protection key + * @param mode mode for this SA (only tunnel mode is supported) + * @param ipcomp IPcomp transform (not supported, use IPCOMP_NONE) + * @param cpi CPI for IPcomp (ignored) + * @param encap enable UDP encapsulation (must be TRUE) + * @param esn Extended Sequence Numbers (currently not supported) + * @param inbound TRUE if this is an inbound SA, FALSE otherwise + * @param src_ts source traffic selector + * @param dst_ts destination traffic selector + * @return the IPsec SA, or NULL if the creation failed + */ +ipsec_sa_t *ipsec_sa_create(u_int32_t spi, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t reqid, mark_t mark, + u_int32_t tfc, lifetime_cfg_t *lifetime, + u_int16_t enc_alg, chunk_t enc_key, + u_int16_t int_alg, chunk_t int_key, + ipsec_mode_t mode, u_int16_t ipcomp, u_int16_t cpi, + bool encap, bool esn, bool inbound, + traffic_selector_t *src_ts, + traffic_selector_t *dst_ts); + +#endif /** IPSEC_SA_H_ @}*/ From 9f7e1899a90c2ffbdbac626d4d58945460eca97c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 11:21:25 +0200 Subject: [PATCH 016/119] Add methods to easily compare IPsec SAs --- src/libipsec/ipsec_sa.c | 22 ++++++++++++++++++++++ src/libipsec/ipsec_sa.h | 29 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/libipsec/ipsec_sa.c b/src/libipsec/ipsec_sa.c index 02fa81354..cccd16404 100644 --- a/src/libipsec/ipsec_sa.c +++ b/src/libipsec/ipsec_sa.c @@ -131,6 +131,25 @@ METHOD(ipsec_sa_t, get_esp_context, esp_context_t*, return this->esp_context; } +METHOD(ipsec_sa_t, match_by_spi_dst, bool, + private_ipsec_sa_t *this, u_int32_t spi, host_t *dst) +{ + return this->spi == spi && this->dst->ip_equals(this->dst, dst); +} + +METHOD(ipsec_sa_t, match_by_spi_src_dst, bool, + private_ipsec_sa_t *this, u_int32_t spi, host_t *src, host_t *dst) +{ + return this->spi == spi && this->src->ip_equals(this->src, src) && + this->dst->ip_equals(this->dst, dst); +} + +METHOD(ipsec_sa_t, match_by_reqid, bool, + private_ipsec_sa_t *this, u_int32_t reqid, bool inbound) +{ + return this->reqid == reqid && this->inbound == inbound; +} + METHOD(ipsec_sa_t, destroy, void, private_ipsec_sa_t *this) { @@ -188,6 +207,9 @@ ipsec_sa_t *ipsec_sa_create(u_int32_t spi, host_t *src, host_t *dst, .get_protocol = _get_protocol, .get_lifetime = _get_lifetime, .is_inbound = _is_inbound, + .match_by_spi_dst = _match_by_spi_dst, + .match_by_spi_src_dst = _match_by_spi_src_dst, + .match_by_reqid = _match_by_reqid, .get_esp_context = _get_esp_context, }, .spi = spi, diff --git a/src/libipsec/ipsec_sa.h b/src/libipsec/ipsec_sa.h index 5cf559a38..5fd03b6e4 100644 --- a/src/libipsec/ipsec_sa.h +++ b/src/libipsec/ipsec_sa.h @@ -95,6 +95,35 @@ struct ipsec_sa_t { */ esp_context_t *(*get_esp_context)(ipsec_sa_t *this); + /** + * Check if this SA matches all given parameters + * + * @param spi SPI + * @param dst destination address + * @return TRUE if this SA matches all parameters, FALSE otherwise + */ + bool (*match_by_spi_dst)(ipsec_sa_t *this, u_int32_t spi, host_t *dst); + + /** + * Check if this SA matches all given parameters + * + * @param spi SPI + * @param src source address + * @param dst destination address + * @return TRUE if this SA matches all parameters, FALSE otherwise + */ + bool (*match_by_spi_src_dst)(ipsec_sa_t *this, u_int32_t spi, host_t *src, + host_t *dst); + + /** + * Check if this SA matches all given parameters + * + * @param reqid reqid + * @param inbound TRUE for inbound SA, FALSE for outbound + * @return TRUE if this SA matches all parameters, FALSE otherwise + */ + bool (*match_by_reqid)(ipsec_sa_t *this, u_int32_t reqid, bool inbound); + /** * Destroy an ipsec_sa_t */ From 914479370ed23aa420a15ef3f19c2c39dce3b133 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 13:21:45 +0200 Subject: [PATCH 017/119] Added IPsec SA manager --- src/libipsec/Android.mk | 3 +- src/libipsec/Makefile.am | 3 +- src/libipsec/ipsec.c | 10 +- src/libipsec/ipsec.h | 11 +- src/libipsec/ipsec_sa_mgr.c | 314 ++++++++++++++++++++++++++++++++++++ src/libipsec/ipsec_sa_mgr.h | 124 ++++++++++++++ 6 files changed, 458 insertions(+), 7 deletions(-) create mode 100644 src/libipsec/ipsec_sa_mgr.c create mode 100644 src/libipsec/ipsec_sa_mgr.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 2c1c1cc93..a5d93dfc3 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -6,7 +6,8 @@ LOCAL_SRC_FILES := \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ -ipsec_sa.c ipsec_sa.h +ipsec_sa.c ipsec_sa.h \ +ipsec_sa_mgr.c ipsec_sa_mgr.h # build libipsec --------------------------------------------------------------- diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 655849632..3de8f82bc 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -4,7 +4,8 @@ libipsec_la_SOURCES = \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ -ipsec_sa.c ipsec_sa.h +ipsec_sa.c ipsec_sa.h \ +ipsec_sa_mgr.c ipsec_sa_mgr.h libipsec_la_LIBADD = diff --git a/src/libipsec/ipsec.c b/src/libipsec/ipsec.c index add3b463a..5ae6c74aa 100644 --- a/src/libipsec/ipsec.c +++ b/src/libipsec/ipsec.c @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager * Copyright (C) 2012 Tobias Brunner * Hochschule fuer Technik Rapperswil * @@ -41,6 +43,7 @@ ipsec_t *ipsec; void libipsec_deinit() { private_ipsec_t *this = (private_ipsec_t*)ipsec; + DESTROY_IF(this->public.sas); free(this); ipsec = NULL; } @@ -52,10 +55,7 @@ bool libipsec_init() { private_ipsec_t *this; - INIT(this, - .public = { - }, - ); + INIT(this); ipsec = &this->public; if (lib->integrity && @@ -64,6 +64,8 @@ bool libipsec_init() DBG1(DBG_LIB, "integrity check of libipsec failed"); return FALSE; } + + this->public.sas = ipsec_sa_mgr_create(); return TRUE; } diff --git a/src/libipsec/ipsec.h b/src/libipsec/ipsec.h index 80bef5426..e4055a8bd 100644 --- a/src/libipsec/ipsec.h +++ b/src/libipsec/ipsec.h @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager * Copyright (C) 2012 Tobias Brunner * Hochschule fuer Technik Rapperswil * @@ -23,15 +25,22 @@ #ifndef IPSEC_H_ #define IPSEC_H_ -typedef struct ipsec_t ipsec_t; +#include "ipsec_sa_mgr.h" #include +typedef struct ipsec_t ipsec_t; + /** * User space IPsec implementation. */ struct ipsec_t { + /** + * IPsec SA manager instance + */ + ipsec_sa_mgr_t *sas; + }; /** diff --git a/src/libipsec/ipsec_sa_mgr.c b/src/libipsec/ipsec_sa_mgr.c new file mode 100644 index 000000000..74fb2ac7e --- /dev/null +++ b/src/libipsec/ipsec_sa_mgr.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec_sa_mgr.h" + +#include +#include +#include +#include +#include + +typedef struct private_ipsec_sa_mgr_t private_ipsec_sa_mgr_t; + +/** + * Private additions to ipsec_sa_mgr_t. + */ +struct private_ipsec_sa_mgr_t { + + /** + * Public members of ipsec_sa_mgr_t. + */ + ipsec_sa_mgr_t public; + + /** + * Installed SAs + */ + linked_list_t *sas; + + /** + * SPIs allocated using get_spi() + */ + hashtable_t *allocated_spis; + + /** + * Mutex used to synchronize access to the SA manager + */ + mutex_t *mutex; + + /** + * RNG used to generate SPIs + */ + rng_t *rng; +}; + +/* + * Used for the hash table of allocated SPIs + */ +static bool spi_equals(u_int32_t *spi, u_int32_t *other_spi) +{ + return *spi == *other_spi; +} + +static u_int spi_hash(u_int32_t *spi) +{ + return chunk_hash(chunk_from_thing(*spi)); +} + +/** + * Flushes all entries + * Must be called with this->mutex held. + */ +static void flush_entries(private_ipsec_sa_mgr_t *this) +{ + enumerator_t *enumerator; + ipsec_sa_t *current; + + DBG2(DBG_ESP, "flushing SAD"); + + enumerator = this->sas->create_enumerator(this->sas); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + this->sas->remove_at(this->sas, enumerator); + current->destroy(current); + } + enumerator->destroy(enumerator); +} + +/* + * Different match functions to find SAs in the linked list + */ +static bool match_entry_by_spi_inbound(ipsec_sa_t *sa, u_int32_t spi, + bool inbound) +{ + return sa->get_spi(sa) == spi && sa->is_inbound(sa) == inbound; +} + +static bool match_entry_by_spi_src_dst(ipsec_sa_t *sa, u_int32_t spi, + host_t *src, host_t *dst) +{ + return sa->match_by_spi_src_dst(sa, spi, src, dst); +} + +/** + * Remove all allocated SPIs + */ +static void flush_allocated_spis(private_ipsec_sa_mgr_t *this) +{ + enumerator_t *enumerator; + u_int32_t *current; + + DBG2(DBG_ESP, "flushing allocated SPIs"); + enumerator = this->allocated_spis->create_enumerator(this->allocated_spis); + while (enumerator->enumerate(enumerator, NULL, (void**)¤t)) + { + this->allocated_spis->remove_at(this->allocated_spis, enumerator); + DBG2(DBG_ESP, " removed allocated SPI %.8x", ntohl(*current)); + free(current); + } + enumerator->destroy(enumerator); +} + +/** + * Pre-allocate an SPI for an inbound SA + */ +static bool allocate_spi(private_ipsec_sa_mgr_t *this, u_int32_t spi) +{ + u_int32_t *spi_alloc; + + if (this->allocated_spis->get(this->allocated_spis, &spi) || + this->sas->find_first(this->sas, (void*)match_entry_by_spi_inbound, + NULL, spi, TRUE) == SUCCESS) + { + return FALSE; + } + spi_alloc = malloc_thing(u_int32_t); + *spi_alloc = spi; + this->allocated_spis->put(this->allocated_spis, spi_alloc, spi_alloc); + return TRUE; +} + +METHOD(ipsec_sa_mgr_t, get_spi, status_t, + private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int8_t protocol, + u_int32_t reqid, u_int32_t *spi) +{ + u_int32_t spi_new; + + DBG2(DBG_ESP, "allocating SPI for reqid {%u}", reqid); + + this->mutex->lock(this->mutex); + if (!this->rng) + { + this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!this->rng) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_ESP, "failed to create RNG for SPI generation"); + return FAILED; + } + } + + do + { + if (!this->rng->get_bytes(this->rng, sizeof(spi_new), + (u_int8_t*)&spi_new)) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_ESP, "failed to allocate SPI for reqid {%u}", reqid); + return FAILED; + } + /* make sure the SPI is valid (not in range 0-255) */ + spi_new |= 0x00000100; + spi_new = htonl(spi_new); + } + while (!allocate_spi(this, spi_new)); + this->mutex->unlock(this->mutex); + + *spi = spi_new; + + DBG2(DBG_ESP, "allocated SPI %.8x for reqid {%u}", ntohl(*spi), reqid); + return SUCCESS; +} + +METHOD(ipsec_sa_mgr_t, add_sa, status_t, + private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int32_t spi, + u_int8_t protocol, u_int32_t reqid, mark_t mark, u_int32_t tfc, + lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key, + u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, u_int16_t ipcomp, + u_int16_t cpi, bool encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts) +{ + ipsec_sa_t *sa_new; + + DBG2(DBG_ESP, "adding SAD entry with SPI %.8x and reqid {%u}", + ntohl(spi), reqid); + DBG2(DBG_ESP, " using encryption algorithm %N with key size %d", + encryption_algorithm_names, enc_alg, enc_key.len * 8); + DBG2(DBG_ESP, " using integrity algorithm %N with key size %d", + integrity_algorithm_names, int_alg, int_key.len * 8); + + sa_new = ipsec_sa_create(spi, src, dst, protocol, reqid, mark, tfc, + lifetime, enc_alg, enc_key, int_alg, int_key, mode, + ipcomp, cpi, encap, esn, inbound, src_ts, dst_ts); + if (!sa_new) + { + DBG1(DBG_ESP, "failed to create SAD entry"); + return FAILED; + } + + this->mutex->lock(this->mutex); + + if (inbound) + { /* remove any pre-allocated SPIs */ + u_int32_t *spi_alloc; + + spi_alloc = this->allocated_spis->remove(this->allocated_spis, &spi); + free(spi_alloc); + } + + if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst, + NULL, spi, src, dst) == SUCCESS) + { + this->mutex->unlock(this->mutex); + DBG1(DBG_ESP, "failed to install SAD entry: already installed"); + sa_new->destroy(sa_new); + return FAILED; + } + this->sas->insert_last(this->sas, sa_new); + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(ipsec_sa_mgr_t, del_sa, status_t, + private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int32_t spi, + u_int8_t protocol, u_int16_t cpi, mark_t mark) +{ + ipsec_sa_t *current, *found = NULL; + enumerator_t *enumerator; + + this->mutex->lock(this->mutex); + enumerator = this->sas->create_enumerator(this->sas); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (match_entry_by_spi_src_dst(current, spi, src, dst)) + { + this->sas->remove_at(this->sas, enumerator); + found = current; + break; + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); + + if (found) + { + DBG2(DBG_ESP, "deleted %sbound SAD entry with SPI %.8x", + found->is_inbound(found) ? "in" : "out", ntohl(spi)); + found->destroy(found); + return SUCCESS; + } + return FAILED; +} + +METHOD(ipsec_sa_mgr_t, flush_sas, status_t, + private_ipsec_sa_mgr_t *this) +{ + this->mutex->lock(this->mutex); + flush_entries(this); + this->mutex->unlock(this->mutex); + return SUCCESS; +} + +METHOD(ipsec_sa_mgr_t, destroy, void, + private_ipsec_sa_mgr_t *this) +{ + this->mutex->lock(this->mutex); + flush_entries(this); + flush_allocated_spis(this); + this->mutex->unlock(this->mutex); + + this->allocated_spis->destroy(this->allocated_spis); + this->sas->destroy(this->sas); + + this->mutex->destroy(this->mutex); + DESTROY_IF(this->rng); + free(this); +} + +/** + * Described in header. + */ +ipsec_sa_mgr_t *ipsec_sa_mgr_create() +{ + private_ipsec_sa_mgr_t *this; + + INIT(this, + .public = { + .get_spi = _get_spi, + .add_sa = _add_sa, + .del_sa = _del_sa, + .flush_sas = _flush_sas, + .destroy = _destroy, + }, + .sas = linked_list_create(), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .allocated_spis = hashtable_create((hashtable_hash_t)spi_hash, + (hashtable_equals_t)spi_equals, 16), + ); + + return &this->public; +} diff --git a/src/libipsec/ipsec_sa_mgr.h b/src/libipsec/ipsec_sa_mgr.h new file mode 100644 index 000000000..0acb0c148 --- /dev/null +++ b/src/libipsec/ipsec_sa_mgr.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_sa_mgr ipsec_sa_mgr + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_SA_MGR_H_ +#define IPSEC_SA_MGR_H_ + +#include "ipsec_sa.h" + +#include +#include +#include +#include + +typedef struct ipsec_sa_mgr_t ipsec_sa_mgr_t; + +/** + * IPsec SA manager + * + * The first methods are modeled after those in kernel_ipsec_t. + */ +struct ipsec_sa_mgr_t { + + /** + * Allocate an SPI for an inbound IPsec SA + * + * @param src source address of the SA + * @param dst destination address of the SA + * @param protocol protocol of the SA (only ESP supported) + * @param reqid reqid for the SA + * @param spi the allocated SPI + * @return SUCCESS of operation successful + */ + status_t (*get_spi)(ipsec_sa_mgr_t *this, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t reqid, u_int32_t *spi); + + /** + * Add a new SA + * + * @param src source address for this SA (gets cloned) + * @param dst destination address for this SA (gets cloned) + * @param spi SPI for this SA + * @param protocol protocol for this SA (only ESP is supported) + * @param reqid reqid for this SA + * @param mark mark for this SA (ignored) + * @param tfc Traffic Flow Confidentiality (not yet supported) + * @param lifetime lifetime for this SA + * @param enc_alg encryption algorithm for this SA + * @param enc_key encryption key for this SA + * @param int_alg integrity protection algorithm + * @param int_key integrity protection key + * @param mode mode for this SA (only tunnel mode is supported) + * @param ipcomp IPcomp transform (not supported, use IPCOMP_NONE) + * @param cpi CPI for IPcomp (ignored) + * @param encap enable UDP encapsulation (must be TRUE) + * @param esn Extended Sequence Numbers (currently not supported) + * @param inbound TRUE if this is an inbound SA, FALSE otherwise + * @param src_ts source traffic selector + * @param dst_ts destination traffic selector + * @return SUCCESS if operation completed + */ + status_t (*add_sa)(ipsec_sa_mgr_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int32_t reqid, + mark_t mark, u_int32_t tfc, lifetime_cfg_t *lifetime, + u_int16_t enc_alg, chunk_t enc_key, u_int16_t int_alg, + chunk_t int_key, ipsec_mode_t mode, u_int16_t ipcomp, + u_int16_t cpi, bool encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts); + + /** + * Delete a previously added SA + * + * @param spi SPI of the SA + * @param src source address of the SA + * @param dst destination address of the SA + * @param protocol protocol of the SA + * @param cpi CPI for IPcomp + * @param mark optional mark + * @return SUCCESS if operation completed + */ + status_t (*del_sa)(ipsec_sa_mgr_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int16_t cpi, + mark_t mark); + + /** + * Flush all SAs + * + * @return SUCCESS if operation completed + */ + status_t (*flush_sas)(ipsec_sa_mgr_t *this); + + /** + * Destroy an ipsec_sa_mgr_t + */ + void (*destroy)(ipsec_sa_mgr_t *this); + +}; + +/** + * Create an ipsec_sa_mgr instance + * + * @return IPsec SA manager instance + */ +ipsec_sa_mgr_t *ipsec_sa_mgr_create(); + +#endif /** IPSEC_SA_MGR_H_ @}*/ From b50f56f326d1e58d13f4287280799236fd239a05 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 13:32:27 +0200 Subject: [PATCH 018/119] Added class to relay IPsec events (like expiration) to listeners Currently, only expiration of IPsec SAs is supported. Later other events for e.g. acquires or changed NAT endpoints could be added. --- src/libipsec/Android.mk | 2 + src/libipsec/Makefile.am | 2 + src/libipsec/ipsec.c | 2 + src/libipsec/ipsec.h | 6 + src/libipsec/ipsec_event_listener.h | 48 +++++++ src/libipsec/ipsec_event_relay.c | 193 ++++++++++++++++++++++++++++ src/libipsec/ipsec_event_relay.h | 79 ++++++++++++ 7 files changed, 332 insertions(+) create mode 100644 src/libipsec/ipsec_event_listener.h create mode 100644 src/libipsec/ipsec_event_relay.c create mode 100644 src/libipsec/ipsec_event_relay.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index a5d93dfc3..024810997 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -6,6 +6,8 @@ LOCAL_SRC_FILES := \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ +ipsec_event_listener.h \ +ipsec_event_relay.c ipsec_event_relay.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 3de8f82bc..c1024930d 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -4,6 +4,8 @@ libipsec_la_SOURCES = \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ +ipsec_event_listener.h \ +ipsec_event_relay.c ipsec_event_relay.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/ipsec.c b/src/libipsec/ipsec.c index 5ae6c74aa..49773abc9 100644 --- a/src/libipsec/ipsec.c +++ b/src/libipsec/ipsec.c @@ -43,6 +43,7 @@ ipsec_t *ipsec; void libipsec_deinit() { private_ipsec_t *this = (private_ipsec_t*)ipsec; + DESTROY_IF(this->public.events); DESTROY_IF(this->public.sas); free(this); ipsec = NULL; @@ -66,6 +67,7 @@ bool libipsec_init() } this->public.sas = ipsec_sa_mgr_create(); + this->public.events = ipsec_event_relay_create(); return TRUE; } diff --git a/src/libipsec/ipsec.h b/src/libipsec/ipsec.h index e4055a8bd..304738170 100644 --- a/src/libipsec/ipsec.h +++ b/src/libipsec/ipsec.h @@ -26,6 +26,7 @@ #define IPSEC_H_ #include "ipsec_sa_mgr.h" +#include "ipsec_event_relay.h" #include @@ -41,6 +42,11 @@ struct ipsec_t { */ ipsec_sa_mgr_t *sas; + /** + * Event relay instance + */ + ipsec_event_relay_t *events; + }; /** diff --git a/src/libipsec/ipsec_event_listener.h b/src/libipsec/ipsec_event_listener.h new file mode 100644 index 000000000..c5c39b0f1 --- /dev/null +++ b/src/libipsec/ipsec_event_listener.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_event_listener ipsec_event_listener + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_EVENT_LISTENER_H_ +#define IPSEC_EVENT_LISTENER_H_ + +typedef struct ipsec_event_listener_t ipsec_event_listener_t; + +#include + +/** + * Listener interface for IPsec events + * + * All methods are optional. + */ +struct ipsec_event_listener_t { + + /** + * Called when the lifetime of an IPsec SA expired + * + * @param reqid reqid of the expired SA + * @param protocol protocol of the expired SA + * @param spi spi of the expired SA + * @param hard TRUE if this is a hard expire, FALSE otherwise + */ + void (*expire)(u_int32_t reqid, u_int8_t protocol, u_int32_t spi, + bool hard); + +}; + +#endif /** IPSEC_EVENT_LISTENER_H_ @}*/ diff --git a/src/libipsec/ipsec_event_relay.c b/src/libipsec/ipsec_event_relay.c new file mode 100644 index 000000000..34222258c --- /dev/null +++ b/src/libipsec/ipsec_event_relay.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec_event_relay.h" + +#include +#include +#include +#include +#include +#include + +typedef struct private_ipsec_event_relay_t private_ipsec_event_relay_t; + +/** + * Private additions to ipsec_event_relay_t. + */ +struct private_ipsec_event_relay_t { + + /** + * Public members + */ + ipsec_event_relay_t public; + + /** + * Registered listeners + */ + linked_list_t *listeners; + + /** + * Lock to safely access the list of listeners + */ + rwlock_t *lock; + + /** + * Blocking queue for events + */ + blocking_queue_t *queue; +}; + +/** + * Helper struct used to manage events in a queue + */ +typedef struct { + + /** + * Type of the event + */ + enum { + IPSEC_EVENT_EXPIRE, + } type; + + /** + * Reqid of the SA, if any + */ + u_int32_t reqid; + + /** + * SPI of the SA, if any + */ + u_int32_t spi; + + /** + * Additional data for specific event types + */ + union { + + struct { + /** Protocol of the SA */ + u_int8_t protocol; + /** TRUE in case of a hard expire */ + bool hard; + } expire; + + } data; + +} ipsec_event_t; + +/** + * Dequeue events and relay them to listeners + */ +static job_requeue_t handle_events(private_ipsec_event_relay_t *this) +{ + enumerator_t *enumerator; + ipsec_event_listener_t *current; + ipsec_event_t *event; + + event = this->queue->dequeue(this->queue); + + this->lock->read_lock(this->lock); + enumerator = this->listeners->create_enumerator(this->listeners); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + switch (event->type) + { + case IPSEC_EVENT_EXPIRE: + if (current->expire) + { + current->expire(event->reqid, event->data.expire.protocol, + event->spi, event->data.expire.hard); + } + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return JOB_REQUEUE_DIRECT; +} + +METHOD(ipsec_event_relay_t, expire, void, + private_ipsec_event_relay_t *this, u_int32_t reqid, u_int8_t protocol, + u_int32_t spi, bool hard) +{ + ipsec_event_t *event; + + INIT(event, + .type = IPSEC_EVENT_EXPIRE, + .reqid = reqid, + .spi = spi, + .data = { + .expire = { + .protocol = protocol, + .hard = hard, + }, + }, + ); + this->queue->enqueue(this->queue, event); +} + +METHOD(ipsec_event_relay_t, register_listener, void, + private_ipsec_event_relay_t *this, ipsec_event_listener_t *listener) +{ + this->lock->write_lock(this->lock); + this->listeners->insert_last(this->listeners, listener); + this->lock->unlock(this->lock); +} + +METHOD(ipsec_event_relay_t, unregister_listener, void, + private_ipsec_event_relay_t *this, ipsec_event_listener_t *listener) +{ + this->lock->write_lock(this->lock); + this->listeners->remove(this->listeners, listener, NULL); + this->lock->unlock(this->lock); +} + +METHOD(ipsec_event_relay_t, destroy, void, + private_ipsec_event_relay_t *this) +{ + this->queue->destroy_function(this->queue, free); + this->listeners->destroy(this->listeners); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +ipsec_event_relay_t *ipsec_event_relay_create() +{ + private_ipsec_event_relay_t *this; + + INIT(this, + .public = { + .expire = _expire, + .register_listener = _register_listener, + .unregister_listener = _unregister_listener, + .destroy = _destroy, + }, + .listeners = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .queue = blocking_queue_create(), + ); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_events, this, + NULL, (callback_job_cancel_t)return_false)); + + return &this->public; +} diff --git a/src/libipsec/ipsec_event_relay.h b/src/libipsec/ipsec_event_relay.h new file mode 100644 index 000000000..c6935d546 --- /dev/null +++ b/src/libipsec/ipsec_event_relay.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_event_relay ipsec_event_relay + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_EVENT_RELAY_H_ +#define IPSEC_EVENT_RELAY_H_ + +#include "ipsec_event_listener.h" + +#include + +typedef struct ipsec_event_relay_t ipsec_event_relay_t; + +/** + * Event relay manager. + * + * Used to notify upper layers about changes + */ +struct ipsec_event_relay_t { + + /** + * Raise an expire event. + * + * @param reqid reqid of the expired IPsec SA + * @param protocol protocol (e.g ESP) of the expired SA + * @param spi SPI of the expired SA + * @param hard TRUE for a hard expire, FALSE otherwise + */ + void (*expire)(ipsec_event_relay_t *this, u_int32_t reqid, + u_int8_t protocol, u_int32_t spi, bool hard); + + /** + * Register a listener to events raised by this manager + * + * @param listener the listener to register + */ + void (*register_listener)(ipsec_event_relay_t *this, + ipsec_event_listener_t *listener); + + /** + * Unregister a listener + * + * @param listener the listener to unregister + */ + void (*unregister_listener)(ipsec_event_relay_t *this, + ipsec_event_listener_t *listener); + + /** + * Destroy an ipsec_event_relay_t + */ + void (*destroy)(ipsec_event_relay_t *this); + +}; + +/** + * Create an ipsec_event_relay_t instance + * + * @return IPsec event relay instance + */ +ipsec_event_relay_t *ipsec_event_relay_create(); + +#endif /** IPSEC_EVENT_RELAY_H_ @}*/ From e6cfd527df482f7bfd881201b75178f0980723b6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 13:54:29 +0200 Subject: [PATCH 019/119] Schedule and relay expiration events for created IPsec SAs --- src/libipsec/ipsec_sa_mgr.c | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/libipsec/ipsec_sa_mgr.c b/src/libipsec/ipsec_sa_mgr.c index 74fb2ac7e..3a851ba5c 100644 --- a/src/libipsec/ipsec_sa_mgr.c +++ b/src/libipsec/ipsec_sa_mgr.c @@ -15,10 +15,12 @@ * for more details. */ +#include "ipsec.h" #include "ipsec_sa_mgr.h" #include #include +#include #include #include #include @@ -56,6 +58,28 @@ struct private_ipsec_sa_mgr_t { rng_t *rng; }; +/** + * Helper struct for expiration events + */ +typedef struct { + + /** + * IPsec SA manager + */ + private_ipsec_sa_mgr_t *manager; + + /** + * SA that expired + */ + ipsec_sa_t *sa; + + /** + * 0 if this is a hard expire, otherwise the offset in s (soft->hard) + */ + u_int32_t hard_offset; + +} ipsec_sa_expired_t; + /* * Used for the hash table of allocated SPIs */ @@ -92,6 +116,11 @@ static void flush_entries(private_ipsec_sa_mgr_t *this) /* * Different match functions to find SAs in the linked list */ +static bool match_entry_by_ptr(ipsec_sa_t *sa, ipsec_sa_t *other) +{ + return sa == other; +} + static bool match_entry_by_spi_inbound(ipsec_sa_t *sa, u_int32_t spi, bool inbound) { @@ -104,6 +133,69 @@ static bool match_entry_by_spi_src_dst(ipsec_sa_t *sa, u_int32_t spi, return sa->match_by_spi_src_dst(sa, spi, src, dst); } +/** + * Callback for expiration events + */ +static job_requeue_t sa_expired(ipsec_sa_expired_t *expired) +{ + private_ipsec_sa_mgr_t *this = expired->manager; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_ptr, + NULL, expired->sa) == SUCCESS) + { + u_int32_t hard_offset = expired->hard_offset; + ipsec_sa_t *sa = expired->sa; + + ipsec->events->expire(ipsec->events, sa->get_reqid(sa), + sa->get_protocol(sa), sa->get_spi(sa), + hard_offset == 0); + if (hard_offset) + { /* soft limit reached, schedule hard expire */ + expired->hard_offset = 0; + this->mutex->unlock(this->mutex); + return JOB_RESCHEDULE(hard_offset); + } + /* hard limit reached */ + this->sas->remove(this->sas, sa, NULL); + sa->destroy(sa); + } + this->mutex->unlock(this->mutex); + return JOB_REQUEUE_NONE; +} + +/** + * Schedule a job to handle IPsec SA expiration + */ +static void schedule_expiration(private_ipsec_sa_mgr_t *this, + ipsec_sa_t *sa) +{ + lifetime_cfg_t *lifetime = sa->get_lifetime(sa); + ipsec_sa_expired_t *expired; + callback_job_t *job; + u_int32_t timeout; + + INIT(expired, + .manager = this, + .sa = sa, + ); + + /* schedule a rekey first, a hard timeout will be scheduled then, if any */ + expired->hard_offset = lifetime->time.life - lifetime->time.rekey; + timeout = lifetime->time.rekey; + + if (lifetime->time.life <= lifetime->time.rekey || + lifetime->time.rekey == 0) + { /* no rekey, schedule hard timeout */ + expired->hard_offset = 0; + timeout = lifetime->time.life; + } + + job = callback_job_create((callback_job_cb_t)sa_expired, expired, + (callback_job_cleanup_t)free, NULL); + lib->scheduler->schedule_job(lib->scheduler, (job_t*)job, timeout); +} + /** * Remove all allocated SPIs */ @@ -228,7 +320,10 @@ METHOD(ipsec_sa_mgr_t, add_sa, status_t, sa_new->destroy(sa_new); return FAILED; } + + schedule_expiration(this, sa_new); this->sas->insert_last(this->sas, sa_new); + this->mutex->unlock(this->mutex); return SUCCESS; } From 9a11bc09bde8491ba64caf4aa20199ef7e5bd532 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 14:05:52 +0200 Subject: [PATCH 020/119] Class representing an IPsec policy added --- src/libipsec/Android.mk | 1 + src/libipsec/Makefile.am | 1 + src/libipsec/ipsec_policy.c | 185 ++++++++++++++++++++++++++++++++++++ src/libipsec/ipsec_policy.h | 115 ++++++++++++++++++++++ 4 files changed, 302 insertions(+) create mode 100644 src/libipsec/ipsec_policy.c create mode 100644 src/libipsec/ipsec_policy.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 024810997..f18fc73f0 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -8,6 +8,7 @@ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ +ipsec_policy.c ipsec_policy.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index c1024930d..4b81d4563 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -6,6 +6,7 @@ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ +ipsec_policy.c ipsec_policy.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/ipsec_policy.c b/src/libipsec/ipsec_policy.c new file mode 100644 index 000000000..e0e154bef --- /dev/null +++ b/src/libipsec/ipsec_policy.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec_policy.h" + +#include + +typedef struct private_ipsec_policy_t private_ipsec_policy_t; + +/** + * Private additions to ipsec_policy_t. + */ +struct private_ipsec_policy_t { + + /** + * Public members + */ + ipsec_policy_t public; + + /** + * SA source address + */ + host_t *src; + + /** + * SA destination address + */ + host_t *dst; + + /** + * Source traffic selector + */ + traffic_selector_t *src_ts; + + /** + * Destination traffic selector + */ + traffic_selector_t *dst_ts; + + /** + * If any of the two TS has a protocol selector we cache it here + */ + u_int8_t protocol; + + /** + * Traffic direction + */ + policy_dir_t direction; + + /** + * Policy type + */ + policy_type_t type; + + /** + * SA configuration + */ + ipsec_sa_cfg_t sa; + + /** + * Mark + */ + mark_t mark; + + /** + * Policy priority + */ + policy_priority_t priority; + + /** + * Reference counter + */ + refcount_t refcount; + +}; + +METHOD(ipsec_policy_t, get_source_ts, traffic_selector_t*, + private_ipsec_policy_t *this) +{ + return this->src_ts; +} + +METHOD(ipsec_policy_t, get_destination_ts, traffic_selector_t*, + private_ipsec_policy_t *this) +{ + return this->dst_ts; +} + +METHOD(ipsec_policy_t, get_reqid, u_int32_t, + private_ipsec_policy_t *this) +{ + return this->sa.reqid; +} + +METHOD(ipsec_policy_t, get_direction, policy_dir_t, + private_ipsec_policy_t *this) +{ + return this->direction; +} + +METHOD(ipsec_policy_t, get_priority, policy_priority_t, + private_ipsec_policy_t *this) +{ + return this->priority; +} + +METHOD(ipsec_policy_t, get_type, policy_type_t, + private_ipsec_policy_t *this) +{ + return this->type; +} + +METHOD(ipsec_policy_t, get_ref, ipsec_policy_t*, + private_ipsec_policy_t *this) +{ + ref_get(&this->refcount); + return &this->public; +} + +METHOD(ipsec_policy_t, destroy, void, + private_ipsec_policy_t *this) +{ + if (ref_put(&this->refcount)) + { + this->src->destroy(this->src); + this->dst->destroy(this->dst); + this->src_ts->destroy(this->src_ts); + this->dst_ts->destroy(this->dst_ts); + free(this); + } +} + +/** + * Described in header. + */ +ipsec_policy_t *ipsec_policy_create(host_t *src, host_t *dst, + traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, + ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority) +{ + private_ipsec_policy_t *this; + + INIT(this, + .public = { + .get_source_ts = _get_source_ts, + .get_destination_ts = _get_destination_ts, + .get_direction = _get_direction, + .get_priority = _get_priority, + .get_reqid = _get_reqid, + .get_type = _get_type, + .get_ref = _get_ref, + .destroy = _destroy, + }, + .src = src->clone(src), + .dst = dst->clone(dst), + .src_ts = src_ts->clone(src_ts), + .dst_ts = dst_ts->clone(dst_ts), + .protocol = max(src_ts->get_protocol(src_ts), + dst_ts->get_protocol(dst_ts)), + .direction = direction, + .type = type, + .sa = *sa, + .mark = mark, + .priority = priority, + .refcount = 1, + ); + + return &this->public; +} diff --git a/src/libipsec/ipsec_policy.h b/src/libipsec/ipsec_policy.h new file mode 100644 index 000000000..0f65b1876 --- /dev/null +++ b/src/libipsec/ipsec_policy.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_policy ipsec_policy + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_POLICY_H +#define IPSEC_POLICY_H + +#include +#include +#include +#include + +typedef struct ipsec_policy_t ipsec_policy_t; + +/** + * IPsec Policy + */ +struct ipsec_policy_t { + + /** + * Get the source traffic selector of this policy + * + * @return the source traffic selector + */ + traffic_selector_t *(*get_source_ts)(ipsec_policy_t *this); + + /** + * Get the destination traffic selector of this policy + * + * @return the destination traffic selector + */ + traffic_selector_t *(*get_destination_ts)(ipsec_policy_t *this); + + /** + * Get the direction of this policy + * + * @return direction + */ + policy_dir_t (*get_direction)(ipsec_policy_t *this); + + /** + * Get the priority of this policy + * + * @return priority + */ + policy_priority_t (*get_priority)(ipsec_policy_t *this); + + /** + * Get the type of this policy (e.g. IPsec) + * + * @return the policy type + */ + policy_type_t (*get_type)(ipsec_policy_t *this); + + /** + * Get the reqid associated to this policy + * + * @return the reqid + */ + u_int32_t (*get_reqid)(ipsec_policy_t *this); + + /** + * Get another reference to this policy + * + * @return additional reference to the policy + */ + ipsec_policy_t *(*get_ref)(ipsec_policy_t *this); + + /** + * Destroy an ipsec_policy_t + */ + void (*destroy)(ipsec_policy_t *this); + +}; + +/** + * Create an ipsec_policy_t instance + * + * @param src source address of SA + * @param dst dest address of SA + * @param src_ts traffic selector to match traffic source + * @param dst_ts traffic selector to match traffic dest + * @param direction direction of traffic, POLICY_(IN|OUT|FWD) + * @param type type of policy, POLICY_(IPSEC|PASS|DROP) + * @param sa details about the SA(s) tied to this policy + * @param mark mark for this policy + * @param priority priority of this policy + * @return ipsec policy instance + */ +ipsec_policy_t *ipsec_policy_create(host_t *src, host_t *dst, + traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, + ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority); + +#endif /** IPSEC_POLICY_H @}*/ From 3b8276b405868c176a936e34579d908dcabd61df Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 14:17:03 +0200 Subject: [PATCH 021/119] Method added to easily compare IPsec policies --- src/libipsec/ipsec_policy.c | 14 ++++++++++++++ src/libipsec/ipsec_policy.h | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/libipsec/ipsec_policy.c b/src/libipsec/ipsec_policy.c index e0e154bef..54bae6a76 100644 --- a/src/libipsec/ipsec_policy.c +++ b/src/libipsec/ipsec_policy.c @@ -88,6 +88,19 @@ struct private_ipsec_policy_t { }; +METHOD(ipsec_policy_t, match, bool, + private_ipsec_policy_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t priority) +{ + return (this->direction == direction && + this->priority == priority && + this->sa.reqid == reqid && + memeq(&this->mark, &mark, sizeof(mark_t)) && + this->src_ts->equals(this->src_ts, src_ts) && + this->dst_ts->equals(this->dst_ts, dst_ts)); +} + METHOD(ipsec_policy_t, get_source_ts, traffic_selector_t*, private_ipsec_policy_t *this) { @@ -158,6 +171,7 @@ ipsec_policy_t *ipsec_policy_create(host_t *src, host_t *dst, INIT(this, .public = { + .match = _match, .get_source_ts = _get_source_ts, .get_destination_ts = _get_destination_ts, .get_direction = _get_direction, diff --git a/src/libipsec/ipsec_policy.h b/src/libipsec/ipsec_policy.h index 0f65b1876..08069307a 100644 --- a/src/libipsec/ipsec_policy.h +++ b/src/libipsec/ipsec_policy.h @@ -84,6 +84,21 @@ struct ipsec_policy_t { */ ipsec_policy_t *(*get_ref)(ipsec_policy_t *this); + /** + * Check if this policy matches all given parameters + * + * @param src_ts source traffic selector + * @param dst_ts destination traffic selector + * @param direction traffic direction + * @param reqid reqid of the policy + * @param mark mark for this policy + * @param prioirty policy priority + * @return TRUE if policy matches all parameters + */ + bool (*match)(ipsec_policy_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, + u_int32_t reqid, mark_t mark, policy_priority_t priority); + /** * Destroy an ipsec_policy_t */ From 7000cf11b1b4e3b2c504951812b48396258e55e2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 14:27:41 +0200 Subject: [PATCH 022/119] IPsec policy manager added This version only provides the very simplest management functions. --- src/libipsec/Android.mk | 1 + src/libipsec/Makefile.am | 1 + src/libipsec/ipsec.c | 2 + src/libipsec/ipsec.h | 6 ++ src/libipsec/ipsec_policy_mgr.c | 162 ++++++++++++++++++++++++++++++++ src/libipsec/ipsec_policy_mgr.h | 106 +++++++++++++++++++++ 6 files changed, 278 insertions(+) create mode 100644 src/libipsec/ipsec_policy_mgr.c create mode 100644 src/libipsec/ipsec_policy_mgr.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index f18fc73f0..269f39b3a 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -9,6 +9,7 @@ esp_packet.c esp_packet.h \ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ ipsec_policy.c ipsec_policy.h \ +ipsec_policy_mgr.c ipsec_policy_mgr.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 4b81d4563..ec5745a83 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -7,6 +7,7 @@ esp_packet.c esp_packet.h \ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ ipsec_policy.c ipsec_policy.h \ +ipsec_policy_mgr.c ipsec_policy_mgr.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/ipsec.c b/src/libipsec/ipsec.c index 49773abc9..5453430a3 100644 --- a/src/libipsec/ipsec.c +++ b/src/libipsec/ipsec.c @@ -44,6 +44,7 @@ void libipsec_deinit() { private_ipsec_t *this = (private_ipsec_t*)ipsec; DESTROY_IF(this->public.events); + DESTROY_IF(this->public.policies); DESTROY_IF(this->public.sas); free(this); ipsec = NULL; @@ -67,6 +68,7 @@ bool libipsec_init() } this->public.sas = ipsec_sa_mgr_create(); + this->public.policies = ipsec_policy_mgr_create(); this->public.events = ipsec_event_relay_create(); return TRUE; } diff --git a/src/libipsec/ipsec.h b/src/libipsec/ipsec.h index 304738170..e8e828d25 100644 --- a/src/libipsec/ipsec.h +++ b/src/libipsec/ipsec.h @@ -26,6 +26,7 @@ #define IPSEC_H_ #include "ipsec_sa_mgr.h" +#include "ipsec_policy_mgr.h" #include "ipsec_event_relay.h" #include @@ -42,6 +43,11 @@ struct ipsec_t { */ ipsec_sa_mgr_t *sas; + /** + * IPsec policy manager instance + */ + ipsec_policy_mgr_t *policies; + /** * Event relay instance */ diff --git a/src/libipsec/ipsec_policy_mgr.c b/src/libipsec/ipsec_policy_mgr.c new file mode 100644 index 000000000..40e69894d --- /dev/null +++ b/src/libipsec/ipsec_policy_mgr.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec_policy_mgr.h" +#include "ipsec_policy.h" + +#include +#include +#include +#include +#include +#include +#include + +typedef struct private_ipsec_policy_mgr_t private_ipsec_policy_mgr_t; + +/** + * Private additions to ipsec_policy_mgr_t. + */ +struct private_ipsec_policy_mgr_t { + + /** + * Public members of ipsec_policy_mgr_t. + */ + ipsec_policy_mgr_t public; + + /** + * Installed policies + */ + linked_list_t *policies; + + /** + * Lock to safely access policies + */ + rwlock_t *lock; + +}; + +static bool match_policy(ipsec_policy_t *policy, ipsec_policy_t *other_policy) +{ + return policy->match(policy, other_policy->get_source_ts(other_policy), + other_policy->get_destination_ts(other_policy), + other_policy->get_direction(other_policy), + other_policy->get_reqid(other_policy), + (mark_t){ .value = 0, }, + other_policy->get_priority(other_policy)); +} + +METHOD(ipsec_policy_mgr_t, add_policy, status_t, + private_ipsec_policy_mgr_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority) +{ + ipsec_policy_t *policy; + + policy = ipsec_policy_create(src, dst, src_ts, dst_ts, direction, type, sa, + mark, priority); + this->lock->write_lock(this->lock); + if (this->policies->find_first(this->policies, (void*)match_policy, + NULL, policy) != SUCCESS) + { + this->policies->insert_last(this->policies, policy); + } + else + { + policy->destroy(policy); + } + this->lock->unlock(this->lock); + return SUCCESS; +} + +METHOD(ipsec_policy_mgr_t, del_policy, status_t, + private_ipsec_policy_mgr_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t priority) +{ + enumerator_t *enumerator; + ipsec_policy_t *current, *found = NULL; + + this->lock->write_lock(this->lock); + enumerator = this->policies->create_enumerator(this->policies); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (current->match(current, src_ts, dst_ts, direction, reqid, + mark, priority)) + { + this->policies->remove_at(this->policies, enumerator); + found = current; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + if (found) + { + found->destroy(found); + return SUCCESS; + } + return FAILED; +} + +METHOD(ipsec_policy_mgr_t, flush_policies, status_t, + private_ipsec_policy_mgr_t *this) +{ + ipsec_policy_t *policy; + + DBG2(DBG_ESP, "flushing policies"); + + this->lock->write_lock(this->lock); + while (this->policies->remove_last(this->policies, + (void**)&policy) == SUCCESS) + { + policy->destroy(policy); + } + this->lock->unlock(this->lock); + return SUCCESS; +} + +METHOD(ipsec_policy_mgr_t, destroy, void, + private_ipsec_policy_mgr_t *this) +{ + flush_policies(this); + this->policies->destroy(this->policies); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +ipsec_policy_mgr_t *ipsec_policy_mgr_create() +{ + private_ipsec_policy_mgr_t *this; + + INIT(this, + .public = { + .add_policy = _add_policy, + .del_policy = _del_policy, + .flush_policies = _flush_policies, + .destroy = _destroy, + }, + .policies = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/libipsec/ipsec_policy_mgr.h b/src/libipsec/ipsec_policy_mgr.h new file mode 100644 index 000000000..0a2f63239 --- /dev/null +++ b/src/libipsec/ipsec_policy_mgr.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_policy_mgr ipsec_policy_mgr + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_POLICY_MGR_H_ +#define IPSEC_POLICY_MGR_H_ + +#include +#include +#include +#include +#include + +typedef struct ipsec_policy_mgr_t ipsec_policy_mgr_t; + +/** + * IPsec policy manager + * + * The first methods are modeled after those in kernel_ipsec_t. + * + * @note Only policies of type POLICY_IPSEC are currently used, also policies + * with direction POLICY_FWD are ignored. Any packets that do not match an + * installed policy will be dropped. + */ +struct ipsec_policy_mgr_t { + + /** + * Add a policy + * + * A policy is always associated to an SA. Traffic which matches a + * policy is handled by the SA with the same reqid. + * + * @param src source address of SA + * @param dst dest address of SA + * @param src_ts traffic selector to match traffic source + * @param dst_ts traffic selector to match traffic dest + * @param direction direction of traffic, POLICY_(IN|OUT|FWD) + * @param type type of policy, POLICY_(IPSEC|PASS|DROP) + * @param sa details about the SA(s) tied to this policy + * @param mark mark for this policy + * @param priority priority of this policy + * @return SUCCESS if operation completed + */ + status_t (*add_policy)(ipsec_policy_mgr_t *this, + host_t *src, host_t *dst, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, + policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority); + + /** + * Remove a policy + * + * @param src_ts traffic selector to match traffic source + * @param dst_ts traffic selector to match traffic dest + * @param direction direction of traffic, POLICY_(IN|OUT|FWD) + * @param reqid unique ID of the associated SA + * @param mark optional mark + * @param priority priority of the policy + * @return SUCCESS if operation completed + */ + status_t (*del_policy)(ipsec_policy_mgr_t *this, + traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, + policy_dir_t direction, u_int32_t reqid, mark_t mark, + policy_priority_t priority); + + /** + * Flush all policies + * + * @return SUCCESS if operation completed + */ + status_t (*flush_policies)(ipsec_policy_mgr_t *this); + + /** + * Destroy an ipsec_policy_mgr_t + */ + void (*destroy)(ipsec_policy_mgr_t *this); + +}; + +/** + * Create an ipsec_policy_mgr instance + * + * @return ipsec_policy_mgr + */ +ipsec_policy_mgr_t *ipsec_policy_mgr_create(); + +#endif /** IPSEC_POLICY_MGR_H_ @}*/ From f1b423831104ccfc38487e2095ddc0c39555dded Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 14:32:03 +0200 Subject: [PATCH 023/119] Implemented a checkout/checkin mechanism for IPsec SAs SAs can only be checked out by a single thread and all other threads block until the SA is checked in again. --- src/libipsec/ipsec_sa_mgr.c | 267 ++++++++++++++++++++++++++++++++---- src/libipsec/ipsec_sa_mgr.h | 43 ++++++ 2 files changed, 285 insertions(+), 25 deletions(-) diff --git a/src/libipsec/ipsec_sa_mgr.c b/src/libipsec/ipsec_sa_mgr.c index 3a851ba5c..e42c77aa5 100644 --- a/src/libipsec/ipsec_sa_mgr.c +++ b/src/libipsec/ipsec_sa_mgr.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,38 @@ struct private_ipsec_sa_mgr_t { rng_t *rng; }; +/** + * Struct to keep track of locked IPsec SAs + */ +typedef struct { + + /** + * IPsec SA + */ + ipsec_sa_t *sa; + + /** + * Set if this SA is currently in use by a thread + */ + bool locked; + + /** + * Condvar used by threads to wait for this entry + */ + condvar_t *condvar; + + /** + * Number of threads waiting for this entry + */ + u_int waiting_threads; + + /** + * Set if this entry is awaiting deletion + */ + bool awaits_deletion; + +} ipsec_sa_entry_t; + /** * Helper struct for expiration events */ @@ -69,9 +102,9 @@ typedef struct { private_ipsec_sa_mgr_t *manager; /** - * SA that expired + * Entry that expired */ - ipsec_sa_t *sa; + ipsec_sa_entry_t *entry; /** * 0 if this is a hard expire, otherwise the offset in s (soft->hard) @@ -93,22 +126,100 @@ static u_int spi_hash(u_int32_t *spi) return chunk_hash(chunk_from_thing(*spi)); } +/** + * Create an SA entry + */ +static ipsec_sa_entry_t *create_entry(ipsec_sa_t *sa) +{ + ipsec_sa_entry_t *this; + + INIT(this, + .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), + .sa = sa, + ); + return this; +} + +/** + * Destroy an SA entry + */ +static void destroy_entry(ipsec_sa_entry_t *entry) +{ + entry->condvar->destroy(entry->condvar); + entry->sa->destroy(entry->sa); + free(entry); +} + +/** + * Makes sure an entry is safe to remove + * Must be called with this->mutex held. + * + * @return TRUE if entry can be removed, FALSE if entry is already +* being removed by another thread + */ +static bool wait_remove_entry(private_ipsec_sa_mgr_t *this, + ipsec_sa_entry_t *entry) +{ + if (entry->awaits_deletion) + { + /* this will be deleted by another thread already */ + return FALSE; + } + entry->awaits_deletion = TRUE; + while (entry->locked) + { + entry->condvar->wait(entry->condvar, this->mutex); + } + while (entry->waiting_threads > 0) + { + entry->condvar->broadcast(entry->condvar); + entry->condvar->wait(entry->condvar, this->mutex); + } + return TRUE; +} + +/** + * Waits until an is available and then locks it. + * Must only be called with this->mutex held + */ +static bool wait_for_entry(private_ipsec_sa_mgr_t *this, + ipsec_sa_entry_t *entry) +{ + while (entry->locked && !entry->awaits_deletion) + { + entry->waiting_threads++; + entry->condvar->wait(entry->condvar, this->mutex); + entry->waiting_threads--; + } + if (entry->awaits_deletion) + { + /* others may still be waiting, */ + entry->condvar->signal(entry->condvar); + return FALSE; + } + entry->locked = TRUE; + return TRUE; +} + /** * Flushes all entries * Must be called with this->mutex held. */ static void flush_entries(private_ipsec_sa_mgr_t *this) { + ipsec_sa_entry_t *current; enumerator_t *enumerator; - ipsec_sa_t *current; DBG2(DBG_ESP, "flushing SAD"); enumerator = this->sas->create_enumerator(this->sas); while (enumerator->enumerate(enumerator, (void**)¤t)) { - this->sas->remove_at(this->sas, enumerator); - current->destroy(current); + if (wait_remove_entry(this, current)) + { + this->sas->remove_at(this->sas, enumerator); + destroy_entry(current); + } } enumerator->destroy(enumerator); } @@ -116,21 +227,65 @@ static void flush_entries(private_ipsec_sa_mgr_t *this) /* * Different match functions to find SAs in the linked list */ -static bool match_entry_by_ptr(ipsec_sa_t *sa, ipsec_sa_t *other) +static bool match_entry_by_ptr(ipsec_sa_entry_t *item, ipsec_sa_entry_t *entry) { - return sa == other; + return item == entry; } -static bool match_entry_by_spi_inbound(ipsec_sa_t *sa, u_int32_t spi, +static bool match_entry_by_sa_ptr(ipsec_sa_entry_t *item, ipsec_sa_t *sa) +{ + return item->sa == sa; +} + +static bool match_entry_by_spi_inbound(ipsec_sa_entry_t *item, u_int32_t spi, bool inbound) { - return sa->get_spi(sa) == spi && sa->is_inbound(sa) == inbound; + return item->sa->get_spi(item->sa) == spi && + item->sa->is_inbound(item->sa) == inbound; } -static bool match_entry_by_spi_src_dst(ipsec_sa_t *sa, u_int32_t spi, +static bool match_entry_by_spi_src_dst(ipsec_sa_entry_t *item, u_int32_t spi, host_t *src, host_t *dst) { - return sa->match_by_spi_src_dst(sa, spi, src, dst); + return item->sa->match_by_spi_src_dst(item->sa, spi, src, dst); +} + +static bool match_entry_by_reqid_inbound(ipsec_sa_entry_t *item, + u_int32_t reqid, bool inbound) +{ + return item->sa->match_by_reqid(item->sa, reqid, inbound); +} + +static bool match_entry_by_spi_dst(ipsec_sa_entry_t *item, u_int32_t spi, + host_t *dst) +{ + return item->sa->match_by_spi_dst(item->sa, spi, dst); +} + +/** + * Remove an entry + */ +static bool remove_entry(private_ipsec_sa_mgr_t *this, ipsec_sa_entry_t *entry) +{ + ipsec_sa_entry_t *current; + enumerator_t *enumerator; + bool removed = FALSE; + + enumerator = this->sas->create_enumerator(this->sas); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + if (current == entry) + { + if (wait_remove_entry(this, current)) + { + this->sas->remove_at(this->sas, enumerator); + removed = TRUE; + } + break; + } + } + enumerator->destroy(enumerator); + return removed; } /** @@ -142,10 +297,10 @@ static job_requeue_t sa_expired(ipsec_sa_expired_t *expired) this->mutex->lock(this->mutex); if (this->sas->find_first(this->sas, (void*)match_entry_by_ptr, - NULL, expired->sa) == SUCCESS) + NULL, expired->entry) == SUCCESS) { u_int32_t hard_offset = expired->hard_offset; - ipsec_sa_t *sa = expired->sa; + ipsec_sa_t *sa = expired->entry->sa; ipsec->events->expire(ipsec->events, sa->get_reqid(sa), sa->get_protocol(sa), sa->get_spi(sa), @@ -157,8 +312,10 @@ static job_requeue_t sa_expired(ipsec_sa_expired_t *expired) return JOB_RESCHEDULE(hard_offset); } /* hard limit reached */ - this->sas->remove(this->sas, sa, NULL); - sa->destroy(sa); + if (remove_entry(this, expired->entry)) + { + destroy_entry(expired->entry); + } } this->mutex->unlock(this->mutex); return JOB_REQUEUE_NONE; @@ -168,16 +325,16 @@ static job_requeue_t sa_expired(ipsec_sa_expired_t *expired) * Schedule a job to handle IPsec SA expiration */ static void schedule_expiration(private_ipsec_sa_mgr_t *this, - ipsec_sa_t *sa) + ipsec_sa_entry_t *entry) { - lifetime_cfg_t *lifetime = sa->get_lifetime(sa); + lifetime_cfg_t *lifetime = entry->sa->get_lifetime(entry->sa); ipsec_sa_expired_t *expired; callback_job_t *job; u_int32_t timeout; INIT(expired, .manager = this, - .sa = sa, + .entry = entry, ); /* schedule a rekey first, a hard timeout will be scheduled then, if any */ @@ -284,6 +441,7 @@ METHOD(ipsec_sa_mgr_t, add_sa, status_t, u_int16_t cpi, bool encap, bool esn, bool inbound, traffic_selector_t *src_ts, traffic_selector_t *dst_ts) { + ipsec_sa_entry_t *entry; ipsec_sa_t *sa_new; DBG2(DBG_ESP, "adding SAD entry with SPI %.8x and reqid {%u}", @@ -321,8 +479,9 @@ METHOD(ipsec_sa_mgr_t, add_sa, status_t, return FAILED; } - schedule_expiration(this, sa_new); - this->sas->insert_last(this->sas, sa_new); + entry = create_entry(sa_new); + schedule_expiration(this, entry); + this->sas->insert_last(this->sas, entry); this->mutex->unlock(this->mutex); return SUCCESS; @@ -332,7 +491,7 @@ METHOD(ipsec_sa_mgr_t, del_sa, status_t, private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark) { - ipsec_sa_t *current, *found = NULL; + ipsec_sa_entry_t *current, *found = NULL; enumerator_t *enumerator; this->mutex->lock(this->mutex); @@ -341,8 +500,11 @@ METHOD(ipsec_sa_mgr_t, del_sa, status_t, { if (match_entry_by_spi_src_dst(current, spi, src, dst)) { - this->sas->remove_at(this->sas, enumerator); - found = current; + if (wait_remove_entry(this, current)) + { + this->sas->remove_at(this->sas, enumerator); + found = current; + } break; } } @@ -352,13 +514,65 @@ METHOD(ipsec_sa_mgr_t, del_sa, status_t, if (found) { DBG2(DBG_ESP, "deleted %sbound SAD entry with SPI %.8x", - found->is_inbound(found) ? "in" : "out", ntohl(spi)); - found->destroy(found); + found->sa->is_inbound(found->sa) ? "in" : "out", ntohl(spi)); + destroy_entry(found); return SUCCESS; } return FAILED; } +METHOD(ipsec_sa_mgr_t, checkout_by_reqid, ipsec_sa_t*, + private_ipsec_sa_mgr_t *this, u_int32_t reqid, bool inbound) +{ + ipsec_sa_entry_t *entry; + ipsec_sa_t *sa = NULL; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_reqid_inbound, + (void**)&entry, reqid, inbound) == SUCCESS && + wait_for_entry(this, entry)) + { + sa = entry->sa; + } + this->mutex->unlock(this->mutex); + return sa; +} + +METHOD(ipsec_sa_mgr_t, checkout_by_spi, ipsec_sa_t*, + private_ipsec_sa_mgr_t *this, u_int32_t spi, host_t *dst) +{ + ipsec_sa_entry_t *entry; + ipsec_sa_t *sa = NULL; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_dst, + (void**)&entry, spi, dst) == SUCCESS && + wait_for_entry(this, entry)) + { + sa = entry->sa; + } + this->mutex->unlock(this->mutex); + return sa; +} + +METHOD(ipsec_sa_mgr_t, checkin, void, + private_ipsec_sa_mgr_t *this, ipsec_sa_t *sa) +{ + ipsec_sa_entry_t *entry; + + this->mutex->lock(this->mutex); + if (this->sas->find_first(this->sas, (void*)match_entry_by_sa_ptr, + (void**)&entry, sa) == SUCCESS) + { + if (entry->locked) + { + entry->locked = FALSE; + entry->condvar->signal(entry->condvar); + } + } + this->mutex->unlock(this->mutex); +} + METHOD(ipsec_sa_mgr_t, flush_sas, status_t, private_ipsec_sa_mgr_t *this) { @@ -396,6 +610,9 @@ ipsec_sa_mgr_t *ipsec_sa_mgr_create() .get_spi = _get_spi, .add_sa = _add_sa, .del_sa = _del_sa, + .checkout_by_spi = _checkout_by_spi, + .checkout_by_reqid = _checkout_by_reqid, + .checkin = _checkin, .flush_sas = _flush_sas, .destroy = _destroy, }, diff --git a/src/libipsec/ipsec_sa_mgr.h b/src/libipsec/ipsec_sa_mgr.h index 0acb0c148..303b36f0e 100644 --- a/src/libipsec/ipsec_sa_mgr.h +++ b/src/libipsec/ipsec_sa_mgr.h @@ -107,6 +107,49 @@ struct ipsec_sa_mgr_t { */ status_t (*flush_sas)(ipsec_sa_mgr_t *this); + /** + * Checkout an installed IPsec SA by SPI and destination address + * Can be used to find the correct SA for an inbound packet. + * + * The matching SA is locked until it is checked in using checkin(). + * If the matching SA is already checked out, this call blocks until the + * SA is checked in. + * + * Since other threads may be waiting for the checked out SA, it should be + * checked in as soon as possible after use. + * + * @param spi SPI (e.g. of an inbound packet) + * @param dst destination address (e.g. of an inbound packet) + * @return the matching IPsec SA, or NULL if none is found + */ + ipsec_sa_t *(*checkout_by_spi)(ipsec_sa_mgr_t *this, u_int32_t spi, + host_t *dst); + + /** + * Checkout an installed IPsec SA by its reqid and inbound/outbound flag. + * Can be used to find the correct SA for an outbound packet. + * + * The matching SA is locked until it is checked in using checkin(). + * If the matching SA is already checked out, this call blocks until the + * SA is checked in. + * + * Since other threads may be waiting for a checked out SA, it should be + * checked in as soon as possible after use. + * + * @param reqid reqid of the SA + * @param inbound TRUE for an inbound SA, FALSE for an outbound SA + * @return the matching IPsec SA, or NULL if none is found + */ + ipsec_sa_t *(*checkout_by_reqid)(ipsec_sa_mgr_t *this, u_int32_t reqid, + bool inbound); + + /** + * Checkin an SA after use. + * + * @param sa checked out SA + */ + void (*checkin)(ipsec_sa_mgr_t *this, ipsec_sa_t *sa); + /** * Destroy an ipsec_sa_mgr_t */ From ed1f0c234fbf8990f8cb55110944ebca0bb4f10f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 14:41:45 +0200 Subject: [PATCH 024/119] Order IPsec policies by a pseudo-priority based on the traffic selectors This allows a simple lookup, i.e. just use the first policy that matches a given IP packet. --- src/libipsec/ipsec_policy_mgr.c | 155 ++++++++++++++++++++++++++------ 1 file changed, 127 insertions(+), 28 deletions(-) diff --git a/src/libipsec/ipsec_policy_mgr.c b/src/libipsec/ipsec_policy_mgr.c index 40e69894d..70447b237 100644 --- a/src/libipsec/ipsec_policy_mgr.c +++ b/src/libipsec/ipsec_policy_mgr.c @@ -19,13 +19,12 @@ #include "ipsec_policy.h" #include -#include -#include -#include #include -#include #include +/** Base priority for installed policies */ +#define PRIO_BASE 512 + typedef struct private_ipsec_policy_mgr_t private_ipsec_policy_mgr_t; /** @@ -39,25 +38,101 @@ struct private_ipsec_policy_mgr_t { ipsec_policy_mgr_t public; /** - * Installed policies + * Installed policies (ipsec_policy_entry_t*) */ linked_list_t *policies; /** - * Lock to safely access policies + * Lock to safely access the list of policies */ rwlock_t *lock; }; -static bool match_policy(ipsec_policy_t *policy, ipsec_policy_t *other_policy) +/** + * Helper struct to store policies in a list sorted by the same pseudo-priority + * used by the NETLINK kernel interface. + */ +typedef struct { + + /** + * Priority used to sort policies + */ + u_int32_t priority; + + /** + * The policy + */ + ipsec_policy_t *policy; + +} ipsec_policy_entry_t; + +/** + * Calculate the pseudo-priority to sort policies. This is the same algorithm + * used by the NETLINK kernel interface (i.e. high priority -> low value). + */ +static u_int32_t calculate_priority(policy_priority_t policy_priority, + traffic_selector_t *src, + traffic_selector_t *dst) { - return policy->match(policy, other_policy->get_source_ts(other_policy), - other_policy->get_destination_ts(other_policy), - other_policy->get_direction(other_policy), - other_policy->get_reqid(other_policy), - (mark_t){ .value = 0, }, - other_policy->get_priority(other_policy)); + u_int32_t priority = PRIO_BASE; + u_int16_t port; + u_int8_t mask, proto; + host_t *net; + + switch (policy_priority) + { + case POLICY_PRIORITY_FALLBACK: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_ROUTED: + priority <<= 1; + /* fall-through */ + case POLICY_PRIORITY_DEFAULT: + break; + } + /* calculate priority based on selector size, small size = high prio */ + src->to_subnet(src, &net, &mask); + priority -= mask; + proto = src->get_protocol(src); + port = net->get_port(net); + net->destroy(net); + + dst->to_subnet(dst, &net, &mask); + priority -= mask; + proto = max(proto, dst->get_protocol(dst)); + port = max(port, net->get_port(net)); + net->destroy(net); + + priority <<= 2; /* make some room for the two flags */ + priority += port ? 0 : 2; + priority += proto ? 0 : 1; + return priority; +} + +/** + * Create a policy entry + */ +static ipsec_policy_entry_t *policy_entry_create(ipsec_policy_t *policy) +{ + ipsec_policy_entry_t *this; + + INIT(this, + .policy = policy, + .priority = calculate_priority(policy->get_priority(policy), + policy->get_source_ts(policy), + policy->get_destination_ts(policy)), + ); + return this; +} + +/** + * Destroy a policy entry + */ +static void policy_entry_destroy(ipsec_policy_entry_t *this) +{ + this->policy->destroy(this->policy); + free(this); } METHOD(ipsec_policy_mgr_t, add_policy, status_t, @@ -66,20 +141,33 @@ METHOD(ipsec_policy_mgr_t, add_policy, status_t, policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, policy_priority_t priority) { + enumerator_t *enumerator; + ipsec_policy_entry_t *entry, *current; ipsec_policy_t *policy; + if (type != POLICY_IPSEC || direction == POLICY_FWD) + { /* we ignore these policies as we currently have no use for them */ + return SUCCESS; + } + + DBG2(DBG_ESP, "adding policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + policy = ipsec_policy_create(src, dst, src_ts, dst_ts, direction, type, sa, mark, priority); + entry = policy_entry_create(policy); + this->lock->write_lock(this->lock); - if (this->policies->find_first(this->policies, (void*)match_policy, - NULL, policy) != SUCCESS) + enumerator = this->policies->create_enumerator(this->policies); + while (enumerator->enumerate(enumerator, (void**)¤t)) { - this->policies->insert_last(this->policies, policy); - } - else - { - policy->destroy(policy); + if (current->priority >= entry->priority) + { + break; + } } + this->policies->insert_before(this->policies, enumerator, entry); + enumerator->destroy(enumerator); this->lock->unlock(this->lock); return SUCCESS; } @@ -87,17 +175,28 @@ METHOD(ipsec_policy_mgr_t, add_policy, status_t, METHOD(ipsec_policy_mgr_t, del_policy, status_t, private_ipsec_policy_mgr_t *this, traffic_selector_t *src_ts, traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, - mark_t mark, policy_priority_t priority) + mark_t mark, policy_priority_t policy_priority) { enumerator_t *enumerator; - ipsec_policy_t *current, *found = NULL; + ipsec_policy_entry_t *current, *found = NULL; + u_int32_t priority; + + if (direction == POLICY_FWD) + { /* we ignore these policies as we currently have no use for them */ + return SUCCESS; + } + DBG2(DBG_ESP, "deleting policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + + priority = calculate_priority(policy_priority, src_ts, dst_ts); this->lock->write_lock(this->lock); enumerator = this->policies->create_enumerator(this->policies); while (enumerator->enumerate(enumerator, (void**)¤t)) { - if (current->match(current, src_ts, dst_ts, direction, reqid, - mark, priority)) + if (current->priority == priority && + current->policy->match(current->policy, src_ts, dst_ts, direction, + reqid, mark, policy_priority)) { this->policies->remove_at(this->policies, enumerator); found = current; @@ -108,7 +207,7 @@ METHOD(ipsec_policy_mgr_t, del_policy, status_t, this->lock->unlock(this->lock); if (found) { - found->destroy(found); + policy_entry_destroy(found); return SUCCESS; } return FAILED; @@ -117,15 +216,15 @@ METHOD(ipsec_policy_mgr_t, del_policy, status_t, METHOD(ipsec_policy_mgr_t, flush_policies, status_t, private_ipsec_policy_mgr_t *this) { - ipsec_policy_t *policy; + ipsec_policy_entry_t *entry; DBG2(DBG_ESP, "flushing policies"); this->lock->write_lock(this->lock); while (this->policies->remove_last(this->policies, - (void**)&policy) == SUCCESS) + (void**)&entry) == SUCCESS) { - policy->destroy(policy); + policy_entry_destroy(entry); } this->lock->unlock(this->lock); return SUCCESS; From 2dd47c244275abc43a597b50b95a792d1aecc3cd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 15:05:27 +0200 Subject: [PATCH 025/119] ip_packet_t parses the header of IP packets --- src/libipsec/Android.mk | 1 + src/libipsec/Makefile.am | 1 + src/libipsec/ip_packet.c | 188 +++++++++++++++++++++++++++++++++++++++ src/libipsec/ip_packet.h | 96 ++++++++++++++++++++ 4 files changed, 286 insertions(+) create mode 100644 src/libipsec/ip_packet.c create mode 100644 src/libipsec/ip_packet.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 269f39b3a..698e12f71 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -6,6 +6,7 @@ LOCAL_SRC_FILES := \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ +ip_packet.c ip_packet.h \ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ ipsec_policy.c ipsec_policy.h \ diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index ec5745a83..3dfa41fba 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -4,6 +4,7 @@ libipsec_la_SOURCES = \ ipsec.c ipsec.h \ esp_context.c esp_context.h \ esp_packet.c esp_packet.h \ +ip_packet.c ip_packet.h \ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ ipsec_policy.c ipsec_policy.h \ diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c new file mode 100644 index 000000000..c78c238c4 --- /dev/null +++ b/src/libipsec/ip_packet.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#include "ip_packet.h" + +#include +#include + +#include +#include +#include + +typedef struct private_ip_packet_t private_ip_packet_t; + +/** + * Private additions to ip_packet_t. + */ +struct private_ip_packet_t { + + /** + * Public members + */ + ip_packet_t public; + + /** + * Source address + */ + host_t *src; + + /** + * Destination address + */ + host_t *dst; + + /** + * IP packet + */ + chunk_t packet; + + /** + * IP version + */ + u_int8_t version; + + /** + * Protocol|Next Header field + */ + u_int8_t next_header; + +}; + +METHOD(ip_packet_t, get_version, u_int8_t, + private_ip_packet_t *this) +{ + return this->version; +} + +METHOD(ip_packet_t, get_source, host_t*, + private_ip_packet_t *this) +{ + return this->src; +} + +METHOD(ip_packet_t, get_destination, host_t*, + private_ip_packet_t *this) +{ + return this->dst; +} + +METHOD(ip_packet_t, get_encoding, chunk_t, + private_ip_packet_t *this) +{ + return this->packet; +} + +METHOD(ip_packet_t, get_next_header, u_int8_t, + private_ip_packet_t *this) +{ + return this->next_header; +} + +METHOD(ip_packet_t, clone, ip_packet_t*, + private_ip_packet_t *this) +{ + return ip_packet_create(this->packet); +} + +METHOD(ip_packet_t, destroy, void, + private_ip_packet_t *this) +{ + this->src->destroy(this->src); + this->dst->destroy(this->dst); + chunk_free(&this->packet); + free(this); +} + +/** + * Described in header. + */ +ip_packet_t *ip_packet_create(chunk_t packet) +{ + private_ip_packet_t *this; + u_int8_t version, next_header; + host_t *src, *dst; + + if (packet.len < 1) + { + DBG1(DBG_ESP, "IP packet too short"); + goto failed; + } + + version = (packet.ptr[0] & 0xf0) >> 4; + + switch (version) + { + case 4: + { + struct iphdr *ip; + + if (packet.len < sizeof(struct iphdr)) + { + DBG1(DBG_ESP, "IPv4 packet too short"); + goto failed; + } + ip = (struct iphdr*)packet.ptr; + src = host_create_from_chunk(AF_INET, + chunk_from_thing(ip->saddr), 0); + dst = host_create_from_chunk(AF_INET, + chunk_from_thing(ip->daddr), 0); + next_header = ip->protocol; + break; + } + case 6: + { + struct ip6_hdr *ip; + + if (packet.len < sizeof(struct ip6_hdr)) + { + DBG1(DBG_ESP, "IPv6 packet too short"); + goto failed; + } + ip = (struct ip6_hdr*)packet.ptr; + src = host_create_from_chunk(AF_INET6, + chunk_from_thing(ip->ip6_src), 0); + dst = host_create_from_chunk(AF_INET6, + chunk_from_thing(ip->ip6_dst), 0); + next_header = ip->ip6_nxt; + } + default: + DBG1(DBG_ESP, "unsupported IP version"); + goto failed; + } + + INIT(this, + .public = { + .get_version = _get_version, + .get_source = _get_source, + .get_destination = _get_destination, + .get_next_header = _get_next_header, + .get_encoding = _get_encoding, + .clone = _clone, + .destroy = _destroy, + }, + .src = src, + .dst = dst, + .packet = packet, + .version = version, + .next_header = next_header, + ); + return &this->public; + +failed: + chunk_free(&packet); + return NULL; +} diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h new file mode 100644 index 000000000..b4fc298ff --- /dev/null +++ b/src/libipsec/ip_packet.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ip_packet ip_packet + * @{ @ingroup libipsec + */ + +#ifndef IP_PACKET_H_ +#define IP_PACKET_H_ + +#include +#include +#include + +typedef struct ip_packet_t ip_packet_t; + +/** + * IP packet + */ +struct ip_packet_t { + + /** + * IP version of this packet + * + * @return ip version + */ + u_int8_t (*get_version)(ip_packet_t *this); + + /** + * Get the source address of this packet + * + * @return source host + */ + host_t *(*get_source)(ip_packet_t *this); + + /** + * Get the destination address of this packet + * + * @return destination host + */ + host_t *(*get_destination)(ip_packet_t *this); + + /** + * Get the protocol (IPv4) or next header (IPv6) field of this packet. + * + * @return protocol|next header field + */ + u_int8_t (*get_next_header)(ip_packet_t *this); + + /** + * Get the complete IP packet (including the header) + * + * @return IP packet (internal data) + */ + chunk_t (*get_encoding)(ip_packet_t *this); + + /** + * Clone the IP packet + * + * @return clone of the packet + */ + ip_packet_t *(*clone)(ip_packet_t *this); + + /** + * Destroy an ip_packet_t + */ + void (*destroy)(ip_packet_t *this); + +}; + +/** + * Create an IP packet out of data from the wire (or decapsulated from another + * packet). + * + * @note The raw IP packet gets either owned by the new object, or destroyed, + * if the data is invalid. + * + * @param packet the IP packet (including header), gets owned + * @return ip_packet_t instance, or NULL if invalid + */ +ip_packet_t *ip_packet_create(chunk_t packet); + +#endif /** IP_PACKET_H_ @}*/ From 2e1a19136d8123e5a8c9aa99afbb4a51d92ec2a6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 15:18:07 +0200 Subject: [PATCH 026/119] IPsec policies can be looked up based on an IP packet --- src/libipsec/ipsec_policy.c | 13 +++++++++++++ src/libipsec/ipsec_policy.h | 10 ++++++++++ src/libipsec/ipsec_policy_mgr.c | 27 ++++++++++++++++++++++++++- src/libipsec/ipsec_policy_mgr.h | 13 +++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/libipsec/ipsec_policy.c b/src/libipsec/ipsec_policy.c index 54bae6a76..af8ea9f9d 100644 --- a/src/libipsec/ipsec_policy.c +++ b/src/libipsec/ipsec_policy.c @@ -101,6 +101,18 @@ METHOD(ipsec_policy_t, match, bool, this->dst_ts->equals(this->dst_ts, dst_ts)); } +METHOD(ipsec_policy_t, match_packet, bool, + private_ipsec_policy_t *this, ip_packet_t *packet) +{ + u_int8_t proto = packet->get_next_header(packet); + host_t *src = packet->get_source(packet), + *dst = packet->get_destination(packet); + + return (!this->protocol || this->protocol == proto) && + this->src_ts->includes(this->src_ts, src) && + this->dst_ts->includes(this->dst_ts, dst); +} + METHOD(ipsec_policy_t, get_source_ts, traffic_selector_t*, private_ipsec_policy_t *this) { @@ -172,6 +184,7 @@ ipsec_policy_t *ipsec_policy_create(host_t *src, host_t *dst, INIT(this, .public = { .match = _match, + .match_packet = _match_packet, .get_source_ts = _get_source_ts, .get_destination_ts = _get_destination_ts, .get_direction = _get_direction, diff --git a/src/libipsec/ipsec_policy.h b/src/libipsec/ipsec_policy.h index 08069307a..67ad0b0ed 100644 --- a/src/libipsec/ipsec_policy.h +++ b/src/libipsec/ipsec_policy.h @@ -23,6 +23,8 @@ #ifndef IPSEC_POLICY_H #define IPSEC_POLICY_H +#include "ip_packet.h" + #include #include #include @@ -99,6 +101,14 @@ struct ipsec_policy_t { traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, mark_t mark, policy_priority_t priority); + /** + * Check if this policy matches the given IP packet + * + * @param packet IP packet + * @return TRUE if policy matches the packet + */ + bool (*match_packet)(ipsec_policy_t *this, ip_packet_t *packet); + /** * Destroy an ipsec_policy_t */ diff --git a/src/libipsec/ipsec_policy_mgr.c b/src/libipsec/ipsec_policy_mgr.c index 70447b237..41ba792c3 100644 --- a/src/libipsec/ipsec_policy_mgr.c +++ b/src/libipsec/ipsec_policy_mgr.c @@ -16,7 +16,6 @@ */ #include "ipsec_policy_mgr.h" -#include "ipsec_policy.h" #include #include @@ -230,6 +229,31 @@ METHOD(ipsec_policy_mgr_t, flush_policies, status_t, return SUCCESS; } +METHOD(ipsec_policy_mgr_t, find_by_packet, ipsec_policy_t*, + private_ipsec_policy_mgr_t *this, ip_packet_t *packet, bool inbound) +{ + enumerator_t *enumerator; + ipsec_policy_entry_t *current; + ipsec_policy_t *found = NULL; + + this->lock->read_lock(this->lock); + enumerator = this->policies->create_enumerator(this->policies); + while (enumerator->enumerate(enumerator, (void**)¤t)) + { + ipsec_policy_t *policy = current->policy; + + if ((inbound == (policy->get_direction(policy) == POLICY_IN)) && + policy->match_packet(policy, packet)) + { + found = policy->get_ref(policy); + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + return found; +} + METHOD(ipsec_policy_mgr_t, destroy, void, private_ipsec_policy_mgr_t *this) { @@ -251,6 +275,7 @@ ipsec_policy_mgr_t *ipsec_policy_mgr_create() .add_policy = _add_policy, .del_policy = _del_policy, .flush_policies = _flush_policies, + .find_by_packet = _find_by_packet, .destroy = _destroy, }, .policies = linked_list_create(), diff --git a/src/libipsec/ipsec_policy_mgr.h b/src/libipsec/ipsec_policy_mgr.h index 0a2f63239..d3ee1074f 100644 --- a/src/libipsec/ipsec_policy_mgr.h +++ b/src/libipsec/ipsec_policy_mgr.h @@ -23,6 +23,9 @@ #ifndef IPSEC_POLICY_MGR_H_ #define IPSEC_POLICY_MGR_H_ +#include "ipsec_policy.h" +#include "ip_packet.h" + #include #include #include @@ -89,6 +92,16 @@ struct ipsec_policy_mgr_t { */ status_t (*flush_policies)(ipsec_policy_mgr_t *this); + /** + * Find the policy that matches the given IP packet best + * + * @param packet IP packet to match + * @param inbound TRUE for an inbound packet + * @return reference to the policy, or NULL if none found + */ + ipsec_policy_t *(*find_by_packet)(ipsec_policy_mgr_t *this, + ip_packet_t *packet, bool inbound); + /** * Destroy an ipsec_policy_mgr_t */ From b37758c41eb3137a4398847e729d6fa3d70617a6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 15:23:00 +0200 Subject: [PATCH 027/119] Represent the payload of an ESP packet as ip_packet_t instead of a chunk_t --- src/libipsec/esp_packet.c | 94 +++++++++++++++++++++++++-------------- src/libipsec/esp_packet.h | 23 ++++++---- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/libipsec/esp_packet.c b/src/libipsec/esp_packet.c index 75d8a7a94..bfcab95eb 100644 --- a/src/libipsec/esp_packet.c +++ b/src/libipsec/esp_packet.c @@ -47,7 +47,7 @@ struct private_esp_packet_t { /** * Payload of this packet */ - chunk_t payload; + ip_packet_t *payload; /** * Next Header info (e.g. IPPROTO_IPIP) @@ -59,7 +59,7 @@ struct private_esp_packet_t { /** * Forward declaration for clone() */ -static private_esp_packet_t *esp_packet_create_empty(packet_t *packet); +static private_esp_packet_t *esp_packet_create_internal(packet_t *packet); METHOD(packet_t, set_source, void, private_esp_packet_t *this, host_t *src) @@ -108,8 +108,8 @@ METHOD(packet_t, clone, packet_t*, { private_esp_packet_t *pkt; - pkt = esp_packet_create_empty(this->packet->clone(this->packet)); - pkt->payload = chunk_clone(this->payload); + pkt = esp_packet_create_internal(this->packet->clone(this->packet)); + pkt->payload = this->payload ? this->payload->clone(this->payload) : NULL; pkt->next_header = this->next_header; return &pkt->public.packet; } @@ -155,35 +155,44 @@ static bool check_padding(chunk_t padding) /** * Remove the padding from the payload and set the next header info */ -static bool remove_padding(private_esp_packet_t *this) +static bool remove_padding(private_esp_packet_t *this, chunk_t plaintext) { u_int8_t next_header, pad_length; - chunk_t padding; + chunk_t padding, payload; bio_reader_t *reader; - reader = bio_reader_create(this->payload); + reader = bio_reader_create(plaintext); if (!reader->read_uint8_end(reader, &next_header) || !reader->read_uint8_end(reader, &pad_length)) { DBG1(DBG_ESP, "parsing ESP payload failed: invalid length"); - reader->destroy(reader); - return FALSE; + goto failed; } if (!reader->read_data_end(reader, pad_length, &padding) || !check_padding(padding)) { DBG1(DBG_ESP, "parsing ESP payload failed: invalid padding"); - reader->destroy(reader); + goto failed; + } + this->payload = ip_packet_create(reader->peek(reader)); + reader->destroy(reader); + if (!this->payload) + { + DBG1(DBG_ESP, "parsing ESP payload failed: unsupported payload"); return FALSE; } - this->payload = reader->peek(reader); this->next_header = next_header; - reader->destroy(reader); + payload = this->payload->get_encoding(this->payload); DBG3(DBG_ESP, "ESP payload:\n payload %B\n padding %B\n " - "padding length = %hhu, next header = %hhu", &this->payload, - &padding, pad_length, this->next_header); + "padding length = %hhu, next header = %hhu", &payload, &padding, + pad_length, this->next_header); return TRUE; + +failed: + reader->destroy(reader); + chunk_free(&plaintext); + return FALSE; } METHOD(esp_packet_t, decrypt, status_t, @@ -191,11 +200,12 @@ METHOD(esp_packet_t, decrypt, status_t, { bio_reader_t *reader; u_int32_t spi, seq; - chunk_t data, iv, icv, ciphertext; + chunk_t data, iv, icv, ciphertext, plaintext; crypter_t *crypter; signer_t *signer; - chunk_free(&this->payload); + DESTROY_IF(this->payload); + this->payload = NULL; data = this->packet->get_data(this->packet); crypter = esp_context->get_crypter(esp_context); @@ -233,15 +243,14 @@ METHOD(esp_packet_t, decrypt, status_t, } esp_context->set_authenticated_seqno(esp_context, seq); - if (!crypter->decrypt(crypter, ciphertext, iv, &this->payload)) + if (!crypter->decrypt(crypter, ciphertext, iv, &plaintext)) { DBG1(DBG_ESP, "ESP decryption failed"); return FAILED; } - if (!remove_padding(this)) + if (!remove_padding(this, plaintext)) { - chunk_free(&this->payload); return PARSE_ERROR; } return SUCCESS; @@ -263,7 +272,7 @@ static void generate_padding(chunk_t padding) METHOD(esp_packet_t, encrypt, status_t, private_esp_packet_t *this, esp_context_t *esp_context, u_int32_t spi) { - chunk_t iv, icv, padding, ciphertext, auth_data; + chunk_t iv, icv, padding, payload, ciphertext, auth_data; bio_writer_t *writer; u_int32_t next_seqno; size_t blocksize, plainlen; @@ -293,7 +302,9 @@ METHOD(esp_packet_t, encrypt, status_t, icv.len = signer->get_block_size(signer); /* plaintext = payload, padding, pad_length, next_header */ - plainlen = this->payload.len + 2; + payload = this->payload ? this->payload->get_encoding(this->payload) + : chunk_empty; + plainlen = payload.len + 2; padding.len = blocksize - (plainlen % blocksize); plainlen += padding.len; @@ -318,7 +329,7 @@ METHOD(esp_packet_t, encrypt, status_t, ciphertext.ptr += ciphertext.len; ciphertext.len = plainlen; - writer->write_data(writer, this->payload); + writer->write_data(writer, payload); padding = writer->skip(writer, padding.len); generate_padding(padding); @@ -327,7 +338,7 @@ METHOD(esp_packet_t, encrypt, status_t, writer->write_uint8(writer, this->next_header); DBG3(DBG_ESP, "ESP before encryption:\n payload = %B\n padding = %B\n " - "padding length = %hhu, next header = %hhu", &this->payload, &padding, + "padding length = %hhu, next header = %hhu", &payload, &padding, (u_int8_t)padding.len, this->next_header); /* encrypt the content inline */ @@ -363,21 +374,31 @@ METHOD(esp_packet_t, get_next_header, u_int8_t, return this->next_header; } -METHOD(esp_packet_t, get_payload, chunk_t, +METHOD(esp_packet_t, get_payload, ip_packet_t*, private_esp_packet_t *this) { return this->payload; } +METHOD(esp_packet_t, extract_payload, ip_packet_t*, + private_esp_packet_t *this) +{ + ip_packet_t *payload; + + payload = this->payload; + this->payload = NULL; + return payload; +} + METHOD2(esp_packet_t, packet_t, destroy, void, private_esp_packet_t *this) { - chunk_free(&this->payload); + DESTROY_IF(this->payload); this->packet->destroy(this->packet); free(this); } -static private_esp_packet_t *esp_packet_create_empty(packet_t *packet) +static private_esp_packet_t *esp_packet_create_internal(packet_t *packet) { private_esp_packet_t *this; @@ -396,11 +417,12 @@ static private_esp_packet_t *esp_packet_create_empty(packet_t *packet) }, .get_source = _get_source, .get_destination = _get_destination, - .get_payload = _get_payload, .get_next_header = _get_next_header, .parse_header = _parse_header, .decrypt = _decrypt, .encrypt = _encrypt, + .get_payload = _get_payload, + .extract_payload = _extract_payload, .destroy = _destroy, }, .packet = packet, @@ -416,7 +438,7 @@ esp_packet_t *esp_packet_create_from_packet(packet_t *packet) { private_esp_packet_t *this; - this = esp_packet_create_empty(packet); + this = esp_packet_create_internal(packet); return &this->public; } @@ -425,16 +447,22 @@ esp_packet_t *esp_packet_create_from_packet(packet_t *packet) * Described in header. */ esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, - chunk_t payload, - u_int8_t next_header) + ip_packet_t *payload) { private_esp_packet_t *this; packet_t *packet; packet = packet_create_from_data(src, dst, chunk_empty); - this = esp_packet_create_empty(packet); - this->next_header = next_header; + this = esp_packet_create_internal(packet); this->payload = payload; - + if (payload) + { + this->next_header = payload->get_version(payload) == 4 ? IPPROTO_IPIP + : IPPROTO_IPV6; + } + else + { + this->next_header = IPPROTO_NONE; + } return &this->public; } diff --git a/src/libipsec/esp_packet.h b/src/libipsec/esp_packet.h index 7dbbd1986..a1d1602c1 100644 --- a/src/libipsec/esp_packet.h +++ b/src/libipsec/esp_packet.h @@ -23,6 +23,7 @@ #ifndef ESP_PACKET_H_ #define ESP_PACKET_H_ +#include "ip_packet.h" #include "esp_context.h" #include @@ -105,12 +106,20 @@ struct esp_packet_t { u_int8_t (*get_next_header)(esp_packet_t *this); /** - * Get the plaintext payload of this packet (e.g. inner IP packet). + * Get the plaintext payload of this packet. * * @return plaintext payload (internal data), - * chunk_empty if not decrypted + * NULL if not decrypted */ - chunk_t (*get_payload)(esp_packet_t *this); + ip_packet_t *(*get_payload)(esp_packet_t *this); + + /** + * Extract the plaintext payload from this packet. + * + * @return plaintext payload (has to be destroyed), + * NULL if not decrypted + */ + ip_packet_t *(*extract_payload)(esp_packet_t *this); /** * Destroy an esp_packet_t @@ -128,17 +137,15 @@ struct esp_packet_t { esp_packet_t *esp_packet_create_from_packet(packet_t *packet); /** - * Create an ESP packet from a plaintext payload (e.g. inner IP packet) + * Create an ESP packet from a plaintext payload * * @param src source address * @param dst destination address - * @param payload plaintext payload (e.g. inner IP packet), gets owned - * @param next_header next header type of the payload (e.g IPPROTO_IPIP) + * @param payload plaintext payload, gets owned * @return esp_packet_t instance */ esp_packet_t *esp_packet_create_from_payload(host_t *src, host_t *dst, - chunk_t payload, - u_int8_t next_header); + ip_packet_t *payload); #endif /** ESP_PACKET_H_ @}*/ From a113d7f29bd2c4c9f378e3644f9309f44e0a08e8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 15:34:51 +0200 Subject: [PATCH 028/119] Added IPsec processor which is responsible for handling in- and outbound packets Two callbacks can be registered that get called when new inbound plaintext and outbound ESP packets have been processed. Inbound ESP and outbound plaintext packets can be queued for processing with two other methods. --- src/libipsec/Android.mk | 1 + src/libipsec/Makefile.am | 1 + src/libipsec/ipsec.c | 2 + src/libipsec/ipsec.h | 6 + src/libipsec/ipsec_processor.c | 324 +++++++++++++++++++++++++++++++++ src/libipsec/ipsec_processor.h | 115 ++++++++++++ 6 files changed, 449 insertions(+) create mode 100644 src/libipsec/ipsec_processor.c create mode 100644 src/libipsec/ipsec_processor.h diff --git a/src/libipsec/Android.mk b/src/libipsec/Android.mk index 698e12f71..81f4632ef 100644 --- a/src/libipsec/Android.mk +++ b/src/libipsec/Android.mk @@ -11,6 +11,7 @@ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ ipsec_policy.c ipsec_policy.h \ ipsec_policy_mgr.c ipsec_policy_mgr.h \ +ipsec_processor.c ipsec_processor.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/Makefile.am b/src/libipsec/Makefile.am index 3dfa41fba..35b8d7916 100644 --- a/src/libipsec/Makefile.am +++ b/src/libipsec/Makefile.am @@ -9,6 +9,7 @@ ipsec_event_listener.h \ ipsec_event_relay.c ipsec_event_relay.h \ ipsec_policy.c ipsec_policy.h \ ipsec_policy_mgr.c ipsec_policy_mgr.h \ +ipsec_processor.c ipsec_processor.h \ ipsec_sa.c ipsec_sa.h \ ipsec_sa_mgr.c ipsec_sa_mgr.h diff --git a/src/libipsec/ipsec.c b/src/libipsec/ipsec.c index 5453430a3..50d9163ea 100644 --- a/src/libipsec/ipsec.c +++ b/src/libipsec/ipsec.c @@ -43,6 +43,7 @@ ipsec_t *ipsec; void libipsec_deinit() { private_ipsec_t *this = (private_ipsec_t*)ipsec; + DESTROY_IF(this->public.processor); DESTROY_IF(this->public.events); DESTROY_IF(this->public.policies); DESTROY_IF(this->public.sas); @@ -70,6 +71,7 @@ bool libipsec_init() this->public.sas = ipsec_sa_mgr_create(); this->public.policies = ipsec_policy_mgr_create(); this->public.events = ipsec_event_relay_create(); + this->public.processor = ipsec_processor_create(); return TRUE; } diff --git a/src/libipsec/ipsec.h b/src/libipsec/ipsec.h index e8e828d25..7ee49432a 100644 --- a/src/libipsec/ipsec.h +++ b/src/libipsec/ipsec.h @@ -28,6 +28,7 @@ #include "ipsec_sa_mgr.h" #include "ipsec_policy_mgr.h" #include "ipsec_event_relay.h" +#include "ipsec_processor.h" #include @@ -53,6 +54,11 @@ struct ipsec_t { */ ipsec_event_relay_t *events; + /** + * IPsec processor instance + */ + ipsec_processor_t *processor; + }; /** diff --git a/src/libipsec/ipsec_processor.c b/src/libipsec/ipsec_processor.c new file mode 100644 index 000000000..a91d9e074 --- /dev/null +++ b/src/libipsec/ipsec_processor.c @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "ipsec.h" +#include "ipsec_processor.h" + +#include +#include +#include +#include +#include + +typedef struct private_ipsec_processor_t private_ipsec_processor_t; + +/** + * Private additions to ipsec_processor_t. + */ +struct private_ipsec_processor_t { + + /** + * Public members + */ + ipsec_processor_t public; + + /** + * Queue for inbound packets (esp_packet_t*) + */ + blocking_queue_t *inbound_queue; + + /** + * Queue for outbound packets (ip_packet_t*) + */ + blocking_queue_t *outbound_queue; + + /** + * Registered inbound callback + */ + struct { + ipsec_inbound_cb_t cb; + void *data; + } inbound; + + /** + * Registered outbound callback + */ + struct { + ipsec_outbound_cb_t cb; + void *data; + } outbound; + + /** + * Lock used to synchronize access to the callbacks + */ + rwlock_t *lock; +}; + +/** + * Deliver an inbound IP packet to the registered listener + */ +static void deliver_inbound(private_ipsec_processor_t *this, + esp_packet_t *packet) +{ + this->lock->read_lock(this->lock); + if (this->inbound.cb) + { + this->inbound.cb(this->inbound.data, packet->extract_payload(packet)); + } + else + { + DBG2(DBG_ESP, "no inbound callback registered, dropping packet"); + } + packet->destroy(packet); + this->lock->unlock(this->lock); +} + +/** + * Processes inbound packets + */ +static job_requeue_t process_inbound(private_ipsec_processor_t *this) +{ + esp_packet_t *packet; + ipsec_sa_t *sa; + u_int8_t next_header; + u_int32_t spi; + + packet = (esp_packet_t*)this->inbound_queue->dequeue(this->inbound_queue); + + if (!packet->parse_header(packet, &spi)) + { + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + + sa = ipsec->sas->checkout_by_spi(ipsec->sas, spi, + packet->get_destination(packet)); + if (!sa) + { + DBG2(DBG_ESP, "inbound ESP packet does not belong to an installed SA"); + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + + if (!sa->is_inbound(sa)) + { + DBG1(DBG_ESP, "error: IPsec SA is not inbound"); + packet->destroy(packet); + ipsec->sas->checkin(ipsec->sas, sa); + return JOB_REQUEUE_DIRECT; + } + + if (packet->decrypt(packet, sa->get_esp_context(sa)) != SUCCESS) + { + ipsec->sas->checkin(ipsec->sas, sa); + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + ipsec->sas->checkin(ipsec->sas, sa); + + next_header = packet->get_next_header(packet); + switch (next_header) + { + case IPPROTO_IPIP: + case IPPROTO_IPV6: + { + ipsec_policy_t *policy; + ip_packet_t *ip_packet; + + ip_packet = packet->get_payload(packet); + policy = ipsec->policies->find_by_packet(ipsec->policies, + ip_packet, TRUE); + if (policy) + { /* TODO-IPSEC: update policy/sa stats? */ + deliver_inbound(this, packet); + policy->destroy(policy); + break; + } + DBG1(DBG_ESP, "discarding inbound IP packet due to policy"); + /* no matching policy found, fall-through */ + } + case IPPROTO_NONE: + /* discard dummy packets */ + /* fall-through */ + default: + packet->destroy(packet); + break; + } + return JOB_REQUEUE_DIRECT; +} + +/** + * Send an ESP packet using the registered outbound callback + */ +static void send_outbound(private_ipsec_processor_t *this, + esp_packet_t *packet) +{ + this->lock->read_lock(this->lock); + if (this->outbound.cb) + { + this->outbound.cb(this->outbound.data, packet); + } + else + { + DBG2(DBG_ESP, "no outbound callback registered, dropping packet"); + packet->destroy(packet); + } + this->lock->unlock(this->lock); +} + +/** + * Processes outbound packets + */ +static job_requeue_t process_outbound(private_ipsec_processor_t *this) +{ + ipsec_policy_t *policy; + esp_packet_t *esp_packet; + ip_packet_t *packet; + ipsec_sa_t *sa; + host_t *src, *dst; + + packet = (ip_packet_t*)this->outbound_queue->dequeue(this->outbound_queue); + + policy = ipsec->policies->find_by_packet(ipsec->policies, packet, FALSE); + if (!policy) + { + DBG1(DBG_ESP, "no matching outbound IPsec policy for %H == %H", + packet->get_source(packet), packet->get_destination(packet)); + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; + } + + sa = ipsec->sas->checkout_by_reqid(ipsec->sas, policy->get_reqid(policy), + FALSE); + if (!sa) + { /* TODO-IPSEC: send an acquire to uppper layer */ + DBG1(DBG_ESP, "could not find an outbound IPsec SA for reqid {%u}, " + "dropping packet", policy->get_reqid(policy)); + packet->destroy(packet); + policy->destroy(policy); + return JOB_REQUEUE_DIRECT; + } + src = sa->get_source(sa); + dst = sa->get_destination(sa); + esp_packet = esp_packet_create_from_payload(src->clone(src), + dst->clone(dst), packet); + if (esp_packet->encrypt(esp_packet, sa->get_esp_context(sa), + sa->get_spi(sa)) != SUCCESS) + { + ipsec->sas->checkin(ipsec->sas, sa); + esp_packet->destroy(esp_packet); + policy->destroy(policy); + return JOB_REQUEUE_DIRECT; + } + /* TODO-IPSEC: update policy/sa counters? */ + ipsec->sas->checkin(ipsec->sas, sa); + policy->destroy(policy); + send_outbound(this, esp_packet); + return JOB_REQUEUE_DIRECT; +} + +METHOD(ipsec_processor_t, queue_inbound, void, + private_ipsec_processor_t *this, esp_packet_t *packet) +{ + this->inbound_queue->enqueue(this->inbound_queue, packet); +} + +METHOD(ipsec_processor_t, queue_outbound, void, + private_ipsec_processor_t *this, ip_packet_t *packet) +{ + this->outbound_queue->enqueue(this->outbound_queue, packet); +} + +METHOD(ipsec_processor_t, register_inbound, void, + private_ipsec_processor_t *this, ipsec_inbound_cb_t cb, void *data) +{ + this->lock->write_lock(this->lock); + this->inbound.cb = cb; + this->inbound.data = data; + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, unregister_inbound, void, + private_ipsec_processor_t *this, ipsec_inbound_cb_t cb) +{ + this->lock->write_lock(this->lock); + if (this->inbound.cb == cb) + { + this->inbound.cb = NULL; + } + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, register_outbound, void, + private_ipsec_processor_t *this, ipsec_outbound_cb_t cb, void *data) +{ + this->lock->write_lock(this->lock); + this->outbound.cb = cb; + this->outbound.data = data; + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, unregister_outbound, void, + private_ipsec_processor_t *this, ipsec_outbound_cb_t cb) +{ + this->lock->write_lock(this->lock); + if (this->outbound.cb == cb) + { + this->outbound.cb = NULL; + } + this->lock->unlock(this->lock); +} + +METHOD(ipsec_processor_t, destroy, void, + private_ipsec_processor_t *this) +{ + this->inbound_queue->destroy_offset(this->inbound_queue, + offsetof(esp_packet_t, destroy)); + this->outbound_queue->destroy_offset(this->outbound_queue, + offsetof(ip_packet_t, destroy)); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +ipsec_processor_t *ipsec_processor_create() +{ + private_ipsec_processor_t *this; + + INIT(this, + .public = { + .queue_inbound = _queue_inbound, + .queue_outbound = _queue_outbound, + .register_inbound = _register_inbound, + .unregister_inbound = _unregister_inbound, + .register_outbound = _register_outbound, + .unregister_outbound = _unregister_outbound, + .destroy = _destroy, + }, + .inbound_queue = blocking_queue_create(), + .outbound_queue = blocking_queue_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)process_inbound, this, + NULL, (callback_job_cancel_t)return_false)); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)process_outbound, this, + NULL, (callback_job_cancel_t)return_false)); + return &this->public; +} diff --git a/src/libipsec/ipsec_processor.h b/src/libipsec/ipsec_processor.h new file mode 100644 index 000000000..0a409828b --- /dev/null +++ b/src/libipsec/ipsec_processor.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup ipsec_processor ipsec_processor + * @{ @ingroup libipsec + */ + +#ifndef IPSEC_PROCESSOR_H_ +#define IPSEC_PROCESSOR_H_ + +#include "ip_packet.h" +#include "esp_packet.h" + +typedef struct ipsec_processor_t ipsec_processor_t; + +/** + * Callback called to deliver an inbound plaintext packet. + * + * @param data data supplied during registration of the callback + * @param packet plaintext IP packet to deliver + */ +typedef void (*ipsec_inbound_cb_t)(void *data, ip_packet_t *packet); + +/** + * Callback called to send an ESP packet. + * + * @note The ESP packet currently comes without IP header (and without UDP + * header in case of UDP encapsulation) + * + * @param data data supplied during registration of the callback + * @param packet ESP packet to send + */ +typedef void (*ipsec_outbound_cb_t)(void *data, esp_packet_t *packet); + +/** + * IPsec processor + */ +struct ipsec_processor_t { + + /** + * Queue an inbound ESP packet for processing. + * + * @param packet the ESP packet to process + */ + void (*queue_inbound)(ipsec_processor_t *this, esp_packet_t *packet); + + /** + * Queue an outbound plaintext IP packet for processing. + * + * @param packet the plaintext IP packet + */ + void (*queue_outbound)(ipsec_processor_t *this, ip_packet_t *packet); + + /** + * Register the callback used to deliver inbound plaintext packets. + * + * @param cb the inbound callback function + * @param data optional data provided to callback + */ + void (*register_inbound)(ipsec_processor_t *this, ipsec_inbound_cb_t cb, + void *data); + + /** + * Unregister a previously registered inbound callback. + * + * @param cb previously registered callback function + */ + void (*unregister_inbound)(ipsec_processor_t *this, + ipsec_inbound_cb_t cb); + + /** + * Register the callback used to send outbound ESP packets. + * + * @param cb the outbound callback function + * @param data optional data provided to callback + */ + void (*register_outbound)(ipsec_processor_t *this, ipsec_outbound_cb_t cb, + void *data); + + /** + * Unregister a previously registered outbound callback. + * + * @param cb previously registered callback function + */ + void (*unregister_outbound)(ipsec_processor_t *this, + ipsec_outbound_cb_t cb); + + /** + * Destroy an ipsec_processor_t. + */ + void (*destroy)(ipsec_processor_t *this); + +}; + +/** + * Create an ipsec_processor_t instance + * + * @return IPsec processor instance + */ +ipsec_processor_t *ipsec_processor_create(); + +#endif /** IPSEC_PROCESSOR_H_ @}*/ From 34400edc37febf3efe41f5c7c111ab93d3abe6c1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 13 Jul 2012 16:12:29 +0200 Subject: [PATCH 029/119] Added utility class to create TUN devices Currently works only on Linux. --- src/libstrongswan/Android.mk | 2 +- src/libstrongswan/Makefile.am | 4 +- src/libstrongswan/utils/tun_device.c | 353 +++++++++++++++++++++++++++ src/libstrongswan/utils/tun_device.h | 112 +++++++++ 4 files changed, 468 insertions(+), 3 deletions(-) create mode 100644 src/libstrongswan/utils/tun_device.c create mode 100644 src/libstrongswan/utils/tun_device.h diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 389120e73..3b2d7eaaa 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -27,7 +27,7 @@ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ utils/linked_list.c utils/blocking_queue.c utils/hashtable.c utils/enumerator.c \ -utils/optionsfrom.c utils/capabilities.c utils/backtrace.c +utils/optionsfrom.c utils/capabilities.c utils/backtrace.c utils/tun_device.c # adding the plugin source files diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 1f27f01ec..a50d7fb90 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -25,7 +25,7 @@ selectors/traffic_selector.c threading/thread.c threading/thread_value.c \ threading/mutex.c threading/semaphore.c threading/rwlock.c threading/spinlock.c \ utils.c utils/host.c utils/packet.c utils/identification.c utils/lexparser.c \ utils/linked_list.c utils/blocking_queue.c utils/hashtable.c utils/enumerator.c \ -utils/optionsfrom.c utils/capabilities.c utils/backtrace.c +utils/optionsfrom.c utils/capabilities.c utils/backtrace.c utils/tun_device.c if USE_DEV_HEADERS strongswan_includedir = ${dev_headers} @@ -61,7 +61,7 @@ threading/mutex.h threading/condvar.h threading/spinlock.h threading/semaphore.h threading/rwlock.h threading/lock_profiler.h utils.h utils/host.h \ utils/packet.h utils/identification.h utils/lexparser.h utils/linked_list.h \ utils/blocking_queue.h utils/hashtable.h utils/enumerator.h utils/optionsfrom.h \ -utils/capabilities.h utils/backtrace.h +utils/capabilities.h utils/backtrace.h utils/tun_device.h endif library.lo : $(top_builddir)/config.status diff --git a/src/libstrongswan/utils/tun_device.c b/src/libstrongswan/utils/tun_device.c new file mode 100644 index 000000000..889fe6247 --- /dev/null +++ b/src/libstrongswan/utils/tun_device.c @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tun_device.h" + +#include +#include +#include + +#define TUN_DEFAULT_MTU 1500 + +typedef struct private_tun_device_t private_tun_device_t; + +struct private_tun_device_t { + + /** + * Public interface + */ + tun_device_t public; + + /** + * The TUN device's file descriptor + */ + int tunfd; + + /** + * Name of the TUN device + */ + char if_name[IFNAMSIZ]; + + /** + * Socket used for ioctl() to set interface addr, ... + */ + int sock; + + /** + * The current MTU + */ + int mtu; +}; + +/** + * Set the sockaddr_t from the given netmask + */ +static void set_netmask(struct ifreq *ifr, int family, u_int8_t netmask) +{ + int len, bytes, bits; + char *target; + + switch (family) + { + case AF_INET: + { + struct sockaddr_in *addr = (struct sockaddr_in*)&ifr->ifr_addr; + addr->sin_family = AF_INET; + target = (char*)&addr->sin_addr; + len = 4; + break; + } + case AF_INET6: + { + struct sockaddr_in6 *addr = (struct sockaddr_in6*)&ifr->ifr_addr; + addr->sin6_family = AF_INET6; + target = (char*)&addr->sin6_addr; + len = 16; + break; + } + default: + return; + } + + bytes = (netmask + 7) / 8; + bits = (bytes * 8) - netmask; + + memset(target, 0xff, bytes); + memset(target + bytes, 0x00, len - bytes); + target[bytes - 1] = bits ? (u_int8_t)(0xff << bits) : 0xff; +} + +METHOD(tun_device_t, set_address, bool, + private_tun_device_t *this, host_t *addr, u_int8_t netmask) +{ + struct ifreq ifr; + int family; + + family = addr->get_family(addr); + if ((netmask > 32 && family == AF_INET) || netmask > 128) + { + DBG1(DBG_LIB, "failed to set address on %s: invalid netmask", + this->if_name); + return FALSE; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + memcpy(&ifr.ifr_addr, addr->get_sockaddr(addr), sizeof(sockaddr_t)); + + if (ioctl(this->sock, SIOCSIFADDR, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to set address on %s: %s", + this->if_name, strerror(errno)); + return FALSE; + } + + set_netmask(&ifr, family, netmask); + + if (ioctl(this->sock, SIOCSIFNETMASK, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to set netmask on %s: %s", + this->if_name, strerror(errno)); + return FALSE; + } + return TRUE; +} + +METHOD(tun_device_t, up, bool, + private_tun_device_t *this) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + + if (ioctl(this->sock, SIOCGIFFLAGS, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to get interface flags for %s: %s", this->if_name, + strerror(errno)); + return FALSE; + } + + ifr.ifr_flags |= IFF_RUNNING | IFF_UP; + + if (ioctl(this->sock, SIOCSIFFLAGS, &ifr) < 0) + { + DBG1(DBG_LIB, "failed to set interface flags on %s: %s", this->if_name, + strerror(errno)); + return FALSE; + } + return TRUE; +} + +METHOD(tun_device_t, set_mtu, bool, + private_tun_device_t *this, int mtu) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + ifr.ifr_mtu = mtu; + + if (ioctl(this->sock, SIOCSIFMTU, &ifr) < 0) + { + return FALSE; + } + this->mtu = mtu; + return TRUE; +} + +METHOD(tun_device_t, get_mtu, int, + private_tun_device_t *this) +{ + struct ifreq ifr; + + if (this->mtu > 0) + { + return this->mtu; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); + this->mtu = TUN_DEFAULT_MTU; + + if (ioctl(this->sock, SIOCGIFMTU, &ifr) == 0) + { + this->mtu = ifr.ifr_mtu; + } + return this->mtu; +} + +METHOD(tun_device_t, get_name, char*, + private_tun_device_t *this) +{ + return this->if_name; +} + +METHOD(tun_device_t, write_packet, bool, + private_tun_device_t *this, chunk_t packet) +{ + ssize_t s; + + s = write(this->tunfd, packet.ptr, packet.len); + if (s < 0) + { + DBG1(DBG_LIB, "failed to write packet to TUN device %s: %s", + this->if_name, strerror(errno)); + return FALSE; + } + else if (s != packet.len) + { + return FALSE; + } + return TRUE; +} + +METHOD(tun_device_t, read_packet, bool, + private_tun_device_t *this, chunk_t *packet) +{ + ssize_t len; + fd_set set; + bool old; + + FD_ZERO(&set); + FD_SET(this->tunfd, &set); + + old = thread_cancelability(TRUE); + len = select(this->tunfd + 1, &set, NULL, NULL, NULL); + thread_cancelability(old); + + if (len < 0) + { + DBG1(DBG_LIB, "select on TUN device %s failed: %s", this->if_name, + strerror(errno)); + return FALSE; + } + /* FIXME: this is quite expensive for lots of small packets, copy from + * local buffer instead? */ + *packet = chunk_alloc(get_mtu(this)); + len = read(this->tunfd, packet->ptr, packet->len); + if (len < 0) + { + DBG1(DBG_LIB, "reading from TUN device %s failed: %s", this->if_name, + strerror(errno)); + chunk_free(packet); + return FALSE; + } + packet->len = len; + return TRUE; +} + +METHOD(tun_device_t, destroy, void, + private_tun_device_t *this) +{ + if (this->tunfd > 0) + { + close(this->tunfd); + } + if (this->sock > 0) + { + close(this->sock); + } + free(this); +} + +/** + * Allocate a TUN device + */ +static int tun_alloc(char dev[IFNAMSIZ]) +{ + struct ifreq ifr; + int fd; + + fd = open("/dev/net/tun", O_RDWR); + if (fd < 0) + { + DBG1(DBG_LIB, "failed to open /dev/net/tun: %s", strerror(errno)); + return fd; + } + + memset(&ifr, 0, sizeof(ifr)); + + /* TUN device, no packet info */ + ifr.ifr_flags = IFF_TUN | IFF_NO_PI; + + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + + if (ioctl(fd, TUNSETIFF, (void*)&ifr) < 0) + { + DBG1(DBG_LIB, "failed to configure TUN device: %s", strerror(errno)); + close(fd); + return -1; + } + strncpy(dev, ifr.ifr_name, IFNAMSIZ); + return fd; +} + +/* + * Described in header + */ +tun_device_t *tun_device_create(const char *name_tmpl) +{ + private_tun_device_t *this; + + INIT(this, + .public = { + .read_packet = _read_packet, + .write_packet = _write_packet, + .get_mtu = _get_mtu, + .set_mtu = _set_mtu, + .get_name = _get_name, + .set_address = _set_address, + .up = _up, + .destroy = _destroy, + }, + .tunfd = -1, + .sock = -1, + ); + + strncpy(this->if_name, name_tmpl ?: "tun%d", IFNAMSIZ); + this->if_name[IFNAMSIZ-1] = '\0'; + + this->tunfd = tun_alloc(this->if_name); + if (this->tunfd < 0) + { + destroy(this); + return NULL; + } + DBG1(DBG_LIB, "created TUN device: %s", this->if_name); + + this->sock = socket(AF_INET, SOCK_DGRAM, 0); + if (this->sock < 0) + { + DBG1(DBG_LIB, "failed to open socket to configure TUN device"); + destroy(this); + return NULL; + } + return &this->public; +} diff --git a/src/libstrongswan/utils/tun_device.h b/src/libstrongswan/utils/tun_device.h new file mode 100644 index 000000000..71af0386b --- /dev/null +++ b/src/libstrongswan/utils/tun_device.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup tun_device tun_device + * @{ @ingroup utils + */ + +#ifndef TUN_DEVICE_H_ +#define TUN_DEVICE_H_ + +#include +#include + +typedef struct tun_device_t tun_device_t; + +/** + * Class to create TUN devices + * + * Creating such a device requires the CAP_NET_ADMIN capability. + * + * @note The implementation is currently very Linux specific + */ +struct tun_device_t { + + /** + * Read a packet from the TUN device + * + * @note This call blocks until a packet is available. It is a thread + * cancellation point. + * + * @param packet the packet read from the device + * @return TRUE if successful + */ + bool (*read_packet)(tun_device_t *this, chunk_t *packet); + + /** + * Write a packet to the TUN device + * + * @param packet the packet to write to the TUN device + * @return TRUE if successful + */ + bool (*write_packet)(tun_device_t *this, chunk_t packet); + + /** + * Set the IP address of the device + * + * @param addr the desired interface address + * @param netmask the netmask to use + * @return TRUE if operation successful + */ + bool (*set_address)(tun_device_t *this, host_t *addr, u_int8_t netmask); + + /** + * Bring the TUN device up + * + * @return TRUE if operation successful + */ + bool (*up)(tun_device_t *this); + + /** + * Set the MTU for this TUN device + * + * @param mtu new MTU + * @return TRUE if operation successful + */ + bool (*set_mtu)(tun_device_t *this, int mtu); + + /** + * Get the current MTU for this TUN device + * + * @return current MTU + */ + int (*get_mtu)(tun_device_t *this); + + /** + * Get the interface name of this device + * + * @return interface name + */ + char *(*get_name)(tun_device_t *this); + + /** + * Destroy a tun_device_t + */ + void (*destroy)(tun_device_t *this); + +}; + +/** + * Create a TUN device using the given name template. + * + * @param name_tmpl name template, defaults to "tun%d" if not given + * @return TUN device + */ +tun_device_t *tun_device_create(const char *name_tmpl); + +#endif /** TUN_DEVICE_H_ @}*/ From b6a071514a4af2902048ad1883e8e7ae701538af Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 11:47:06 +0200 Subject: [PATCH 030/119] Fixed ip_packet_t if IPv6 is not available --- configure.in | 1 + src/libipsec/ip_packet.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/configure.in b/configure.in index 3a7d5b76c..4555c75d6 100644 --- a/configure.in +++ b/configure.in @@ -431,6 +431,7 @@ AC_CHECK_FUNCS(prctl mallinfo getpass closefrom getpwnam_r getgrnam_r) AC_CHECK_HEADERS(sys/sockio.h glob.h) AC_CHECK_HEADERS(net/pfkeyv2.h netipsec/ipsec.h netinet6/ipsec.h linux/udp.h) +AC_CHECK_HEADERS(netinet/ip6.h) AC_CHECK_MEMBERS([struct sockaddr.sa_len], [], [], [ diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index c78c238c4..4593ba5c8 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -21,7 +21,9 @@ #include #include +#ifdef HAVE_NETINET_IP6_H #include +#endif typedef struct private_ip_packet_t private_ip_packet_t; @@ -143,6 +145,7 @@ ip_packet_t *ip_packet_create(chunk_t packet) next_header = ip->protocol; break; } +#ifdef HAVE_NETINET_IP6_H case 6: { struct ip6_hdr *ip; @@ -159,6 +162,7 @@ ip_packet_t *ip_packet_create(chunk_t packet) chunk_from_thing(ip->ip6_dst), 0); next_header = ip->ip6_nxt; } +#endif /* HAVE_NETINET_IP6_H */ default: DBG1(DBG_ESP, "unsupported IP version"); goto failed; From d62d5d7c2e826516484fa96e2cc296dc017a99e8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 15:31:36 +0200 Subject: [PATCH 031/119] Use strongSwan logo as icon Due to the transparency and black font this is probably not optimal yet. --- src/frontends/android/AndroidManifest.xml | 2 +- .../android/res/drawable-hdpi/ic_launcher.png | Bin 4147 -> 0 bytes .../android/res/drawable-hdpi/strongswan.png | Bin 0 -> 3910 bytes .../android/res/drawable-ldpi/ic_launcher.png | Bin 1723 -> 0 bytes .../android/res/drawable-ldpi/strongswan.png | Bin 0 -> 1574 bytes .../android/res/drawable-mdpi/ic_launcher.png | Bin 2574 -> 0 bytes .../android/res/drawable-mdpi/strongswan.png | Bin 0 -> 2562 bytes .../android/res/drawable-xhdpi/strongswan.png | Bin 0 -> 6320 bytes 8 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 src/frontends/android/res/drawable-hdpi/ic_launcher.png create mode 100644 src/frontends/android/res/drawable-hdpi/strongswan.png delete mode 100644 src/frontends/android/res/drawable-ldpi/ic_launcher.png create mode 100644 src/frontends/android/res/drawable-ldpi/strongswan.png delete mode 100644 src/frontends/android/res/drawable-mdpi/ic_launcher.png create mode 100644 src/frontends/android/res/drawable-mdpi/strongswan.png create mode 100644 src/frontends/android/res/drawable-xhdpi/strongswan.png diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 5b1d03d7c..79ebd663c 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -8,7 +8,7 @@ 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 diff --git a/src/frontends/android/res/drawable-hdpi/strongswan.png b/src/frontends/android/res/drawable-hdpi/strongswan.png new file mode 100644 index 0000000000000000000000000000000000000000..08bfcd61c2ad8a884076d74cdba3c7973a0edf6b GIT binary patch literal 3910 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 UPg`x9@&Et;07*qoM6N<$f_goP+5i9m literal 0 HcmV?d00001 diff --git a/src/frontends/android/res/drawable-ldpi/ic_launcher.png b/src/frontends/android/res/drawable-ldpi/ic_launcher.png deleted file mode 100644 index 1095584ec21f71cd0afc9e0993aa2209671b590c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1723 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 Y7s~gC)Il-Z=Kufz07*qoM6N<$f-N@-pa1{> literal 0 HcmV?d00001 diff --git a/src/frontends/android/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index a07c69fa5a0f4da5d5efe96eea12a543154dbab6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2574 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 Y0dG!q0Nu@GZU6uP07*qoM6N<$f*OkQ*8l(j literal 0 HcmV?d00001 diff --git a/src/frontends/android/res/drawable-xhdpi/strongswan.png b/src/frontends/android/res/drawable-xhdpi/strongswan.png new file mode 100644 index 0000000000000000000000000000000000000000..4a86e9150cbf8af82d0d36344c9181f5696b1686 GIT binary patch literal 6320 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 LSQAtuZyNePYP?A+ literal 0 HcmV?d00001 From cb887af4cf6ea2a670c93caae8732a657e0df3d4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 16:00:01 +0200 Subject: [PATCH 032/119] Moved JNI helper macros to a separate file Also initialize a reference to the CharonVpnService class during JNI_OnLoad, which allows us later to call methods from C to Java. --- .../android/jni/libandroidbridge/Android.mk | 1 + .../jni/libandroidbridge/android_jni.c | 49 +++++++++++++++++++ .../jni/libandroidbridge/android_jni.h | 46 +++++++++++++++++ .../jni/libandroidbridge/charonservice.c | 19 +++---- 4 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 src/frontends/android/jni/libandroidbridge/android_jni.c create mode 100644 src/frontends/android/jni/libandroidbridge/android_jni.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index 3b8b98b86..d84004a4a 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -3,6 +3,7 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ +android_jni.c android_jni.h \ charonservice.c # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c new file mode 100644 index 000000000..32957451f --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "android_jni.h" + +#include + +/** + * JVM + */ +static JavaVM *android_jvm; + +jclass *android_charonvpnservice_class; + +/** + * Called when this library is loaded by the JVM + */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) +{ + JNIEnv *env; + + android_jvm = vm; + + if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) + { + return -1; + } + + android_charonvpnservice_class = + (*env)->NewGlobalRef(env, (*env)->FindClass(env, + JNI_PACKAGE_STRING "/CharonVpnService")); + + return JNI_VERSION_1_6; +} + diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h new file mode 100644 index 000000000..8f8d30299 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup android_jni android_jni + * @{ @ingroup libandroidbridge + */ + +#ifndef ANDROID_JNI_H_ +#define ANDROID_JNI_H_ + +#include + +#define JNI_PACKAGE org_strongswan_android +#define JNI_PACKAGE_STRING "org/strongswan/android" + +#define JNI_METHOD_PP(pack, klass, name, ret, ...) \ + ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) + +#define JNI_METHOD_P(pack, klass, name, ret, ...) \ + JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__) + +#define JNI_METHOD(klass, name, ret, ...) \ + JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__) + +/** + * Java classes + * Initialized in JNI_OnLoad() + */ +extern jclass *android_charonvpnservice_class; + +#endif /** ANDROID_JNI_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 424d50d24..758f154fe 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -1,4 +1,6 @@ /* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager * Copyright (C) 2012 Tobias Brunner * Hochschule fuer Technik Rapperswil * @@ -15,28 +17,19 @@ #include #include -#include +#include "android_jni.h" + +#include #include #include -#include #include -#define JNI_PACKAGE org_strongswan_android - -#define JNI_METHOD_PP(pack, klass, name, ret, ...) \ - ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) - -#define JNI_METHOD_P(pack, klass, name, ret, ...) \ - JNI_METHOD_PP(pack, klass, name, ret, ##__VA_ARGS__) - -#define JNI_METHOD(klass, name, ret, ...) \ - JNI_METHOD_P(JNI_PACKAGE, klass, name, ret, ##__VA_ARGS__) /** * hook in library for debugging messages */ -extern void (*dbg) (debug_t group, level_t level, char *fmt, ...); +extern void (*dbg)(debug_t group, level_t level, char *fmt, ...); /** * Logging hook for library logs, using android specific logging From f83f65be08de72547bb06e5f2127d70f041d3299 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 16:06:12 +0200 Subject: [PATCH 033/119] Added functions to attach/detach native threads to the JVM Even though native threads are automatically detached from the JVM with help of a thread-local destructor it is recommended to detach as soon as possible as local JNI references are not freed until a thread detaches. --- .../jni/libandroidbridge/android_jni.c | 45 +++++++++++++++++++ .../jni/libandroidbridge/android_jni.h | 19 ++++++++ 2 files changed, 64 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c index 32957451f..0acebbbcd 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.c +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -18,6 +18,7 @@ #include "android_jni.h" #include +#include /** * JVM @@ -26,6 +27,40 @@ static JavaVM *android_jvm; jclass *android_charonvpnservice_class; +/** + * Thread-local variable. Only used because of the destructor + */ +static thread_value_t *androidjni_threadlocal; + +/** + * Thread-local destructor to ensure that a native thread is detached + * from the JVM even if androidjni_detach_thread() is not called. + */ +static void attached_thread_cleanup(void *arg) +{ + (*android_jvm)->DetachCurrentThread(android_jvm); +} + +/* + * Described in header + */ +void androidjni_attach_thread(JNIEnv **env) +{ + (*android_jvm)->AttachCurrentThread(android_jvm, env, NULL); + /* use a thread-local value with a destructor that automatically detaches + * the thread from the JVM before it terminates, if not done manually */ + androidjni_threadlocal->set(androidjni_threadlocal, (void*)*env); +} + +/* + * Described in header + */ +void androidjni_detach_thread() +{ + androidjni_threadlocal->set(androidjni_threadlocal, NULL); + (*android_jvm)->DetachCurrentThread(android_jvm); +} + /** * Called when this library is loaded by the JVM */ @@ -40,6 +75,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) return -1; } + androidjni_threadlocal = thread_value_create(attached_thread_cleanup); + android_charonvpnservice_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, JNI_PACKAGE_STRING "/CharonVpnService")); @@ -47,3 +84,11 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) return JNI_VERSION_1_6; } +/** + * Called when this library is unloaded by the JVM + */ +void JNI_OnUnload(JavaVM *vm, void *reserved) +{ + androidjni_threadlocal->destroy(androidjni_threadlocal); +} + diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 8f8d30299..2a8d3a71b 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -43,4 +43,23 @@ */ extern jclass *android_charonvpnservice_class; +/** + * Attach the current thread to the JVM + * + * As local JNI references are not freed until the thread detaches + * androidjni_detach_thread() should be called as soon as possible. + * If it is not called a thread-local destructor ensures that the + * thread is at least detached as soon as it terminates. + * + * @param env JNIEnv + */ +void androidjni_attach_thread(JNIEnv **env); + +/** + * Detach the current thread from the JVM + * + * Call this as soon as possible to ensure that local JNI references are freed. + */ +void androidjni_detach_thread(); + #endif /** ANDROID_JNI_H_ @}*/ From 6f11e9413441b2aacf6d4f75944a504ad778de15 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 16:14:34 +0200 Subject: [PATCH 034/119] Global charonservice_t object added to libandroidbridge This is later used to call Java methods on CharonVpnService via JNI. --- .../android/jni/libandroidbridge/Android.mk | 2 +- .../jni/libandroidbridge/charonservice.c | 49 ++++++++++++++++++- .../jni/libandroidbridge/charonservice.h | 46 +++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/frontends/android/jni/libandroidbridge/charonservice.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index d84004a4a..fe6cd6c1d 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -4,7 +4,7 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ -charonservice.c +charonservice.c charonservice.h # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 758f154fe..c86554981 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -18,6 +18,7 @@ #include #include +#include "charonservice.h" #include "android_jni.h" #include @@ -25,6 +26,23 @@ #include #include +typedef struct private_charonservice_t private_charonservice_t; + +/** + * private data of charonservice + */ +struct private_charonservice_t { + + /** + * public interface + */ + charonservice_t public; +}; + +/** + * Single instance of charonservice_t. + */ +charonservice_t *charonservice; /** * hook in library for debugging messages @@ -60,6 +78,31 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) } } +/** + * Initialize the charonservice object + */ +static void charonservice_init() +{ + private_charonservice_t *this; + + INIT(this, + .public = { + }, + ); + charonservice = &this->public; +} + +/** + * Deinitialize the charonservice object + */ +static void charonservice_deinit() +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + + free(this); + charonservice = NULL; +} + /** * Initialize charon and the libraries via JNI */ @@ -90,10 +133,13 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) return; } + charonservice_init(); + if (!libcharon_init("charon") || !charon->initialize(charon, PLUGINS)) { libcharon_deinit(); + charonservice_deinit(); libipsec_deinit(); libhydra_deinit(); library_deinit(); @@ -105,11 +151,12 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) } /** - * Initialize charon and the libraries via JNI + * Deinitialize charon and all libraries */ JNI_METHOD(CharonVpnService, deinitializeCharon, void) { libcharon_deinit(); + charonservice_deinit(); libipsec_deinit(); libhydra_deinit(); library_deinit(); diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h new file mode 100644 index 000000000..0788ced94 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup libandroidbridge libandroidbridge + * + * @defgroup charonservice charonservice + * @{ @ingroup libandroidbridge + */ + +#ifndef CHARONSERVICE_H_ +#define CHARONSERVICE_H_ + +typedef struct charonservice_t charonservice_t; + +/** + * Public interface of charonservice. + * + * Used to communicate with CharonVpnService via JNI + */ +struct charonservice_t { + +}; + +/** + * The single instance of charonservice_t. + * + * Set between JNI calls to initializeCharon() and deinitializeCharon(). + */ +extern charonservice_t *charonservice; + +#endif /** CHARONSERVICE_H_ @}*/ From 8bf30276431afb6d02dc1d648fe0d2ab4a8d94ce Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 17:03:22 +0200 Subject: [PATCH 035/119] Moved CharonVpnService to logic sub-package --- src/frontends/android/AndroidManifest.xml | 8 ++++++-- src/frontends/android/jni/libandroidbridge/android_jni.h | 4 ++-- .../strongswan/android/{ => logic}/CharonVpnService.java | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) rename src/frontends/android/src/org/strongswan/android/{ => logic}/CharonVpnService.java (96%) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 79ebd663c..c6f4e14e4 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -5,6 +5,7 @@ android:versionName="1.0" > + - + - + diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 2a8d3a71b..77c33f73e 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -25,8 +25,8 @@ #include -#define JNI_PACKAGE org_strongswan_android -#define JNI_PACKAGE_STRING "org/strongswan/android" +#define JNI_PACKAGE org_strongswan_android_logic +#define JNI_PACKAGE_STRING "org/strongswan/android/logic" #define JNI_METHOD_PP(pack, klass, name, ret, ...) \ ret Java_##pack##_##klass##_##name(JNIEnv *env, jobject this, ##__VA_ARGS__) diff --git a/src/frontends/android/src/org/strongswan/android/CharonVpnService.java b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java similarity index 96% rename from src/frontends/android/src/org/strongswan/android/CharonVpnService.java rename to src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java index d917d3eae..b32f9ae87 100644 --- a/src/frontends/android/src/org/strongswan/android/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -1,4 +1,4 @@ -package org.strongswan.android; +package org.strongswan.android.logic; import android.content.Intent; import android.net.VpnService; From 441dde9ee9ed4e1d8c4ab9e3998a3e52985c49d1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 14 Jul 2012 17:12:07 +0200 Subject: [PATCH 036/119] Moved main Activity to ui sub-package Also force portrait orientation. --- src/frontends/android/AndroidManifest.xml | 6 ++++-- .../{strongSwanActivity.java => ui/MainActivity.java} | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) rename src/frontends/android/src/org/strongswan/android/{strongSwanActivity.java => ui/MainActivity.java} (89%) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index c6f4e14e4..836ef0f78 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -12,8 +12,10 @@ android:icon="@drawable/strongswan" android:label="@string/app_name" > + android:name=".ui.MainActivity" + android:label="@string/app_name" + android:launchMode="singleTop" + android:screenOrientation="portrait" > diff --git a/src/frontends/android/src/org/strongswan/android/strongSwanActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java similarity index 89% rename from src/frontends/android/src/org/strongswan/android/strongSwanActivity.java rename to src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index fabf71897..ae3a5e3ef 100644 --- a/src/frontends/android/src/org/strongswan/android/strongSwanActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -1,11 +1,11 @@ -package org.strongswan.android; +package org.strongswan.android.ui; import android.app.Activity; import android.content.Intent; import android.net.VpnService; import android.os.Bundle; -public class strongSwanActivity extends Activity +public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) From b17b495f2e633caaecf6c47f937a3e2cf3070e35 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 18:39:42 +0200 Subject: [PATCH 037/119] Replaced launcher icon with a more appropriate one --- src/frontends/android/AndroidManifest.xml | 2 +- .../android/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 4953 bytes .../android/res/drawable-hdpi/strongswan.png | Bin 3910 -> 0 bytes .../android/res/drawable-ldpi/strongswan.png | Bin 1574 -> 0 bytes .../android/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 3149 bytes .../android/res/drawable-mdpi/strongswan.png | Bin 2562 -> 0 bytes .../android/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 6786 bytes .../android/res/drawable-xhdpi/strongswan.png | Bin 6320 -> 0 bytes 8 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/frontends/android/res/drawable-hdpi/ic_launcher.png delete mode 100644 src/frontends/android/res/drawable-hdpi/strongswan.png delete mode 100644 src/frontends/android/res/drawable-ldpi/strongswan.png create mode 100644 src/frontends/android/res/drawable-mdpi/ic_launcher.png delete mode 100644 src/frontends/android/res/drawable-mdpi/strongswan.png create mode 100644 src/frontends/android/res/drawable-xhdpi/ic_launcher.png delete mode 100644 src/frontends/android/res/drawable-xhdpi/strongswan.png diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 836ef0f78..3a1008218 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -9,7 +9,7 @@ @5EmigP^+&3!tM^f#Ep)Fvo?W|kB@~3gFi@}d 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 literal 0 HcmV?d00001 diff --git a/src/frontends/android/res/drawable-hdpi/strongswan.png b/src/frontends/android/res/drawable-hdpi/strongswan.png deleted file mode 100644 index 08bfcd61c2ad8a884076d74cdba3c7973a0edf6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3910 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 UPg`x9@&Et;07*qoM6N<$f_goP+5i9m diff --git a/src/frontends/android/res/drawable-ldpi/strongswan.png b/src/frontends/android/res/drawable-ldpi/strongswan.png deleted file mode 100644 index 3f213e43b82bf698d57eb3b2319a30e35e0f0ec4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1574 zcmV+>2HE+EP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_000HKNklA*Vp~PJmxWvdHipYA$`a1;k|6u%m4rvg0`ed)tGk{`GSHlM?5T?4@z`p zk20k61{vQnsZHiT1YRT1pNSY9qLmcKZ#hGsgv>ON4CyBf+a}ZmR0A@sgmeb!sgyrv zFRgmC`^k%iKN(a37(y6^PNW!U{jU_txHg*oF*2m@1O5VRsQ6+xof4Q`!VCcK+;a}9 zN(jv?62g)I8MQi7K*}HWv(IvOrHnsD#8PP4d75ic=%ev5n>gJ z(^P56kbcFCJu?5?J%wzDRta4JTRyj0SuBI~DF#jgd?)>dUC$2Yo@uz@tsGVk;M@=K zeyQZHf?aC1f$MWB)X~uqw70iccXV{bW?nwqEnv4Rld-vJS26_aeb1$r1N?TNf6L2j zlfR#gNfq06nQc#Tytmno4JsC1zoSAO9UY;WeOuBRpbE3IE2gZzbGZ291w|ig8IQCq z?}=s^M>7dK9Tmyo@`Z)&h0d=m+Fzfu>Zi};-`sI(YF#wF0KL%6z)QDvtG&G)V3DMh znbiRkl6D%r=i%h1u17z=T;t-oK*YT9`438TUnx=yL@p2yjTH&~O?@;=uRYC)9e;c< zF&G%NTh8VF@YM^2iZeQ8@L2paP1y(Def?vEg2u*1oO2~0Dd|y3>&@(jms0mn`A61X z_f0h=GjnM9lyQ1#NYBa`@6-jnzc}R3iZ}vRe&h*lCXWoH)MT`=e|dbui~7l!BYiDe zmGFk423MWf*?6VrmXL-0{ryywAL{Jv{0$Hq7W>=gMa4H>96mc*6|gLa9-4?aup&lH z(Wot8Q=v2w7c#iCw#v>f4z=~n6tQyt%D_TTZ1weMe#*d&Z-%uhNcML`R2;lPZ#EW)7LNNO_=sK#QepYpf=`>+Jc33c~hB`yyZ&&ynU_S zz8Nk7-UpT#m@px|ynf5F=UYcg+1{SNMz^zT`EplsD(F1}5M^kU!*pdHKvdS`Js_WWZt-&~dT zj~E=1^st!?OR6!mF-Z$CGn2+;)><90Z9J})SY;?wm5l8tr>yA2U|pb8*1z`4byer_ z_6pD@X^nI4XT80>lkQ@}rt$rpwSpZ4YX>tc^Bw+sU9he3BU=MVQa{insiCj0 zZtnH;;;Bzb*xTvw}IcGv2}KQLM(eNmD(v!tX? z1B9zS*vHtbueynE{NfniZ|SmjzD%N6akO+1dFM@BJQ0dElTy zD`t-uo&VL3P8WtgcdgU{MgTUrBvc|Yt|i@)UsU^zIV&R7GVH*tJ@0>jF-^HJ5#N zcXy@AoTF0i;lF3R!TGzrGxhfNo}PX#z&TfGlaaKuWNcr+iR@e#Gl0E&_f`nRWr)6y z4_gTo$}xH`S5+qtm}!ve_?`!+4(H0%+e-5sWM*)lwf}MRGoSR{tk(IThk49n9{;!a Y7s~gC)Il-Z=Kufz07*qoM6N<$f-N@-pa1{> diff --git a/src/frontends/android/res/drawable-mdpi/ic_launcher.png b/src/frontends/android/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..200ee9677c7313d44bcdfe01c1e7a270cdfe0dca GIT binary patch literal 3149 zcmV-T46^fyP)Ojx!w}BRZwE;>fgPJB89(MyM#& z5eY)2Pz?onD3Tfo(*6{nF10T6632@t$JF{ zTefTgt!7zNgqP4_9=v$``0=0c@xhVriYGcdJGUJ;aNrkA`d{CA>#g4vB?Jg0q^K;Y z;H0Xms>LTxoY*oZAtVV{TwI)4U0r?G8*jXEK3OH<<@%+UURq1xS6nRoMMpnl{d-LM zKb4e}SRp9}kPwr+AUdravm6jipEYaNSH+r#4;Vrl7#K;?NKVVYqSb{{PD+iGLIy_gptfIzd2{sm?W_* z#5nZ`^ojLF0r3Ds>MU<}Br}JCTJa#??Up%}7X^0ZH;t4tW+)`ZNL(!oi2}pLO87!j zly>gixfXH>>M$H_qKwQX^Gc|$+Hfewld0k|hKZo2ikb{TAqgX? z8!1yjG0qEK7_q1*py)y}hgeyxt%gcq8S45{YhHB{i?EVdvaqt8!tN0vl=NaMchr0% zIg|2gN)I`2@D1$?!`(n({RrNc2$~8)2ZVCl$&FGM9pA?%1ZWPRvoyD=)ENp5kR|QaiFGOP1`NGiT0?LZ1~} zN~lP|QRGc9T<&FMWm!2QzeSLdHA1dsekokCsgbh`nMW9{gs2EE29hB~QsxvT<+s`h z31i)~Y12AxKYx^z2RPASPfySJ2}KYQP~0t(%H+zzB8+%Pgja=?drJQz#X5 zGfEJ#A|nl6Gmgx@5c-{@?THv{45egh+GkQALE#;fO3~4H2JyQd#SDKh(zMAKvL_H6 zE)61s-Sm=nGxkF!H0FSQ$4%&cWe*C>R>X}$8ajqfj~4~+HR6j8w=fM%oSruWz6x&6 zdIwV1R=JRwkyhttqc1pw$h}YFT0$a>3iR2T$P|dFXpI<0RLe%|rLF7qIPD(c8P>)=Ds-MbBIOQ-d*WI`WX|?}xVNLG*6g!BC3AxPCHD z|9A;{8E|gq%XNRh%v|sQxJYFTVvXdJ0Z$0x5+qpb2y0bl5A3k%*XxlaZUz z5F}+Ih8;1<0ihJBRzRw1Y#br+)xF4ybR(#jaB;yNAMAJD0ne)Mz|ErOq}>gh24{xb z63X|`x6t~?TKMbUgflLF$c1h7GTGGw=}yEBega3Ni_f|-Sl~gJ=%)1#CAG=WM}<6P zh3Gx<2@08j8MYh*6Jaje1KYj#!1L4(V07`5(ub#rI1Xtf36Zymo2T*%n7HLRG~JU4 zv+*OOKd=I^S>@2fCeEI2hJEu}^reSb6hhzB@rY+-z}DkWQ3%nMnlQ@JB6KyiAotmq zQA}1D(6h)sS%Gh!5BKW3xnJb9m|Ga7f15-<-jUy!69vFmSvOA@h@+gR3yE4213hnP+sO+SAD&W7EnK&55* zK;jGrZ=8pr#WirW@wO~DzvPNBtMqN={=nw z;4LoV?w&h8bR(hV!Cbl&!8>Z9b@>nrM^f8bw7yC*xC2bESi))11snh6rVncxrD!^rqQ}R}^v&&<>kFAm3zDr~oZ%^aT+9%5*f%sfKQx+q64~ zF1lfZURuJwbZ^oHUABkOmQEDk{WX~1XT0YTc{;jaJ9Sn*Ku|l*6GLh}ph=;bKt7-8 zVVJZ1CyDA}i!j{{o%a$$OwNq-(b4dnOvFQBL{7CK5e`|KdyKLp_dgY28)JfeRhPDk z`UK>1uJ*rX#NL#|b4LVdVU--%T>Lb?rdwnk2_)%lRmR%)pG zaxL$uwhHGjNLm4YKK%nfecsH4LnB*>s-1YL^T=z}v5;%k%#u!pI*U+p9@)*f`0wiZ n;qL}6euH~E>HjP7c{=_JePu|o#41A}00000NkvXXu0mjfUSa51 literal 0 HcmV?d00001 diff --git a/src/frontends/android/res/drawable-mdpi/strongswan.png b/src/frontends/android/res/drawable-mdpi/strongswan.png deleted file mode 100644 index 8a753a6de0016112d2e9c5023b3065c47d1ec36e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2562 zcmV+d3jOtoP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_000SgrY8OMMB^S*aU(lkj6O;TubQ@WwWk#3X$lUubNL@nbo=;#b0(+_GtFwTez`oU#n zTp7fLabK#8IO+g8>QFEVOIeyQgHWjg?M<6DO}eE?Z*K1Lp2rXO-lVamG%nxrzxTtt zocEmn^PFcrFo!wJVGeVc!yIOhf!O-%6xn1xi@tG4CgZ%TgQ9 zrY4>&K3u<^w~p`T2v^qSDb9%VL_x%ju%ge5=**ILLam0C2U(R;MMq`>)$bb$Y8j8ak2|7d37Mu9X&`~M0t^3r z-E*1LtOdxpb)QRt1?+@9~1SEMGq<&nyB*h`=v2}TT?FLV+Z z0*##4iPPycW=3ys?|B6oWX<(O^gXcliqo0Us|Eur{{=)sKTFZSt^8sKZ%zN`>NgD) zzy9V!g=>~(y%msz6_9?5bGN6W-2-RN^$H!-U6Y=%3ui5yPN!pLwp5aUQQ&w_Pfrox zJM9}+rwnK~YimFA{l3TYeCi$QeCG7E*Bva}^|k|r zH46*gB2ZsND_pB(b1aq>ybtV>s-xbpj_mbH@{%3 z+j3sz&?+Y^yp`5n-(qO&RRw5kYb$Ss6a$t6*8{77)sm7XwS{II+OmGh!h$s?yqI4! zk{+FoiGg~@Q_V4YTSJZ|9S7<|o@|YCDlR(4JOOrw5L5o5xMxvo&Zi!_G;zE(;^M=# z&MnMaYsaamF>Y2#2wDPq;9Cq04!-_f+#mo3JE*}81sr|qj9O|L>^-z5MjdEk1wy) z&3kjSkC&=`Q86bJ8Z`?hZJCMsn8%MKUCpVu%yFjGS<}!5w!7AQzw2MKl<4m6K0E$v zT{9<38>)UOq{Fq&PRCF~u|&?v`YsObWWsULq|cpGkDgXiAwUU)6EpS5&Ula~3FLT@ie5XmrG zJQ1leZ+0xHXmFnEZkg~u>}d_3`BmRzQ*pa1oxV)4B{IKlUwv@J-K%TD?JwkX*;vUu zCIgPtI6c20NQ9n*7c>-nvY}wIY9uPBm*K``TV`9#SvC9C0b77YLRN`yGDJ<}Uum0g zx4kL*^!)%FnmI#nPtPmBJ-|JiI=gNxnccbjqHuBhSV;kJsMgv2sW=6xHfG?3mX7%# zHSUR~3!?!QiX*Q^KAld_6rkJ%*;JWq*i}Lwv#LTWn#%{;GZAyQY`wneWap_jFA9pK z6Xu*VGcPF#WX&v)6gMbz_w@Yc-B)+M@KQr?|L>P3m#-fw+Rs-cc(E}I5|ITZCz^Af zE034dQ1X^nxg;!xdScJQ_$h{=v$HcMNzS>@dq2_J+e=SR&vYWo#I7P$eEQ77Z8hlI zKzh0&Gma+Qy(<~fTwk;g z4JTc@2@A@h2`|_@Sm3IWl9gQ9(drAviwnXZ{Bl_`6PVqPw^cwWX_cg(0EfD}yKx7a z-%{?XD&#ZCjbVm08$fqg$yckz0LGrZD17|ERN_x9IeP_oS<)lGW*{r+dNcdEnQfEw zKH!f?@0Hm{K@Us@DjlwIpHSI6B!kuo&$UO2G!~4%EQ#~brE&GN#@W>z>;L=wUEP%8S^_(UKNVM99eRYKeX0#fIR|lCG24h?Nyo08RkUOB$^t-!j32ty}P8*>ygf z+x;pX6`%nNbZbY{_rPqsxJ{cjb(q;zW@f#;z4x79&5A5~KEmP^Hz0=L;CKw~zKHx? zV$axX&AWN?<{44Q&fDlq#}o!bQ!#E9=`5A3hrk2#k&W1q}csaz{%i|84_PxA2V zi`i3ptxW4Di0OB!+|!5u>q9WDt;6gZL#Ii`p6i%= zDtoTh^@6aEm9tuW?|)DJ?9yfawd}7PlFb^&K6=Nj#vi@q3v&m%bC|;%<}imj%%OsR Y0dG!q0Nu@GZU6uP07*qoM6N<$f*OkQ*8l(j diff --git a/src/frontends/android/res/drawable-xhdpi/ic_launcher.png b/src/frontends/android/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..2eb6db1b6ac4bdd0bf9e56196e8f8195593bd483 GIT binary patch literal 6786 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 literal 0 HcmV?d00001 diff --git a/src/frontends/android/res/drawable-xhdpi/strongswan.png b/src/frontends/android/res/drawable-xhdpi/strongswan.png deleted file mode 100644 index 4a86e9150cbf8af82d0d36344c9181f5696b1686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6320 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 LSQAtuZyNePYP?A+ From 3d9127da61022f59e2ef975c4b5f4ecec012811c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 18:40:30 +0200 Subject: [PATCH 038/119] Added class to move around VPN profiles in the Android App --- .../strongswan/android/data/VpnProfile.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/data/VpnProfile.java diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java new file mode 100644 index 000000000..8dc5f2499 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.data; + +public class VpnProfile +{ + private String mName, mGateway, mUsername, mPassword, mCertificate; + private long mId = -1; + + public long getId() + { + return mId; + } + + public void setId(long id) + { + this.mId = id; + } + + public String getName() + { + return mName; + } + + public void setName(String name) + { + this.mName = name; + } + + public String getGateway() + { + return mGateway; + } + + public void setGateway(String gateway) + { + this.mGateway = gateway; + } + + public String getUsername() + { + return mUsername; + } + + public void setUsername(String username) + { + this.mUsername = username; + } + + public String getPassword() + { + return mPassword; + } + + public void setPassword(String password) + { + this.mPassword = password; + } + + public String getCertificateAlias() + { + return mCertificate; + } + + public void setCertificateAlias(String certificate) + { + this.mCertificate = certificate; + } + + @Override + public String toString() + { + return mName; + } +} From d799cbf676f4e0b9d1e07767d34cf7f21db59284 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 18:50:23 +0200 Subject: [PATCH 039/119] Added class to simplify access to database of VPN profiles --- .../android/data/VpnProfileDataSource.java | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java new file mode 100644 index 000000000..18632ad6f --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfileDataSource.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.data; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +public class VpnProfileDataSource +{ + private static final String TAG = VpnProfileDataSource.class.getSimpleName(); + public static final String KEY_ID = "_id"; + public static final String KEY_NAME = "name"; + public static final String KEY_GATEWAY = "gateway"; + public static final String KEY_USERNAME = "username"; + public static final String KEY_PASSWORD = "password"; + public static final String KEY_CERTIFICATE = "certificate"; + + private DatabaseHelper mDbHelper; + private SQLiteDatabase mDatabase; + private final Context mContext; + + private static final String DATABASE_NAME = "strongswan.db"; + private static final String TABLE_VPNPROFILE = "vpnprofile"; + + private static final int DATABASE_VERSION = 1; + + public static final String DATABASE_CREATE = + "CREATE TABLE " + TABLE_VPNPROFILE + " (" + + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + + KEY_NAME + " TEXT NOT NULL," + + KEY_GATEWAY + " TEXT NOT NULL," + + KEY_USERNAME + " TEXT NOT NULL," + + KEY_PASSWORD + " TEXT," + + KEY_CERTIFICATE + " TEXT" + + ");"; + private final String[] ALL_COLUMNS = new String[] { + KEY_ID, + KEY_NAME, + KEY_GATEWAY, + KEY_USERNAME, + KEY_PASSWORD, + KEY_CERTIFICATE + }; + + private static class DatabaseHelper extends SQLiteOpenHelper + { + public DatabaseHelper(Context context) + { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase database) + { + database.execSQL(DATABASE_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) + { + Log.w(TAG, "Upgrading database from version " + oldVersion + + " to " + newVersion + ", which will destroy all old data"); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_VPNPROFILE); + onCreate(db); + } + } + + /** + * Construct a new VPN profile data source. The context is used to + * open/create the database. + * @param context context used to access the database + */ + public VpnProfileDataSource(Context context) + { + this.mContext = context; + } + + /** + * Open the VPN profile data source. The database is automatically created + * if it does not yet exist. If that fails an exception is thrown. + * @return itself (allows to chain initialization calls) + * @throws SQLException if the database could not be opened or created + */ + public VpnProfileDataSource open() throws SQLException + { + if (mDbHelper == null) + { + mDbHelper = new DatabaseHelper(mContext); + mDatabase = mDbHelper.getWritableDatabase(); + } + return this; + } + + /** + * Close the data source. + */ + public void close() + { + if (mDbHelper != null) + { + mDbHelper.close(); + mDbHelper = null; + } + } + + /** + * Insert the given VPN profile into the database. On success the Id of + * the object is updated and the object returned. + * + * @param profile the profile to add + * @return the added VPN profile or null, if failed + */ + public VpnProfile insertProfile(VpnProfile profile) + { + ContentValues values = ContentValuesFromVpnProfile(profile); + long insertId = mDatabase.insert(TABLE_VPNPROFILE, null, values); + if (insertId == -1) + { + return null; + } + profile.setId(insertId); + return profile; + } + + /** + * Updates the given VPN profile in the database. + * @param profile the profile to update + * @return true if update succeeded, false otherwise + */ + public boolean updateVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + ContentValues values = ContentValuesFromVpnProfile(profile); + return mDatabase.update(TABLE_VPNPROFILE, values, KEY_ID + " = " + id, null) > 0; + } + + /** + * Delete the given VPN profile from the database. + * @param profile the profile to delete + * @return true if deleted, false otherwise + */ + public boolean deleteVpnProfile(VpnProfile profile) + { + long id = profile.getId(); + return mDatabase.delete(TABLE_VPNPROFILE, KEY_ID + " = " + id, null) > 0; + } + + /** + * Get a single VPN profile from the database. + * @param id the ID of the VPN profile + * @return the profile or null, if not found + */ + public VpnProfile getVpnProfile(long id) + { + VpnProfile profile = null; + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, + KEY_ID + "=" + id, null, null, null, null); + if (cursor.moveToFirst()) + { + profile = VpnProfileFromCursor(cursor); + } + cursor.close(); + return profile; + } + + /** + * Get a list of all VPN profiles stored in the database. + * @return list of VPN profiles + */ + public List getAllVpnProfiles() + { + List vpnProfiles = new ArrayList(); + + Cursor cursor = mDatabase.query(TABLE_VPNPROFILE, ALL_COLUMNS, null, null, null, null, null); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) + { + VpnProfile vpnProfile = VpnProfileFromCursor(cursor); + vpnProfiles.add(vpnProfile); + cursor.moveToNext(); + } + cursor.close(); + return vpnProfiles; + } + + private VpnProfile VpnProfileFromCursor(Cursor cursor) + { + VpnProfile profile = new VpnProfile(); + profile.setId(cursor.getLong(cursor.getColumnIndex(KEY_ID))); + profile.setName(cursor.getString(cursor.getColumnIndex(KEY_NAME))); + profile.setGateway(cursor.getString(cursor.getColumnIndex(KEY_GATEWAY))); + profile.setUsername(cursor.getString(cursor.getColumnIndex(KEY_USERNAME))); + profile.setPassword(cursor.getString(cursor.getColumnIndex(KEY_PASSWORD))); + profile.setCertificateAlias(cursor.getString(cursor.getColumnIndex(KEY_CERTIFICATE))); + return profile; + } + + private ContentValues ContentValuesFromVpnProfile(VpnProfile profile) + { + ContentValues values = new ContentValues(); + values.put(KEY_NAME, profile.getName()); + values.put(KEY_GATEWAY, profile.getGateway()); + values.put(KEY_USERNAME, profile.getUsername()); + values.put(KEY_PASSWORD, profile.getPassword()); + values.put(KEY_CERTIFICATE, profile.getCertificateAlias()); + return values; + } +} From 03a5a63c0310ecf4dd61e3b2eac652226a185e4b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:02:50 +0200 Subject: [PATCH 040/119] Added a custom adapter and layout to display VPN profiles in a ListView --- .../android/res/layout/profile_list_item.xml | 49 +++++++++++ src/frontends/android/res/values/strings.xml | 22 ++++- .../android/ui/adapter/VpnProfileAdapter.java | 88 +++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/res/layout/profile_list_item.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java diff --git a/src/frontends/android/res/layout/profile_list_item.xml b/src/frontends/android/res/layout/profile_list_item.xml new file mode 100644 index 000000000..f55c8357a --- /dev/null +++ b/src/frontends/android/res/layout/profile_list_item.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index f4df7613e..83c09a6a1 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -1,7 +1,27 @@ + + + strongSwan VPN Client Hello World, strongSwanActivity! - strongSwan + + Gateway: + Username: \ No newline at end of file diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java new file mode 100644 index 000000000..39e3e586a --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/VpnProfileAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui.adapter; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +public class VpnProfileAdapter extends ArrayAdapter +{ + private final int resource; + private final List items; + + public VpnProfileAdapter(Context context, int resource, + List items) + { + super(context, resource, items); + this.resource = resource; + this.items = items; + sortItems(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View vpnProfileView; + if (convertView != null) + { + vpnProfileView = convertView; + } + else + { + LayoutInflater inflater = LayoutInflater.from(getContext()); + vpnProfileView = inflater.inflate(resource, null); + } + VpnProfile profile = getItem(position); + TextView tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_name); + tv.setText(profile.getName()); + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_gateway); + tv.setText(getContext().getString(R.string.profile_gateway_label) + " " + profile.getGateway()); + tv = (TextView)vpnProfileView.findViewById(R.id.profile_item_username); + tv.setText(getContext().getString(R.string.profile_username_label) + " " + profile.getUsername()); + return vpnProfileView; + } + + @Override + public void notifyDataSetChanged() + { + sortItems(); + super.notifyDataSetChanged(); + } + + private void sortItems() + { + Collections.sort(this.items, new Comparator() { + @Override + public int compare(VpnProfile lhs, VpnProfile rhs) + { + return lhs.getName().compareToIgnoreCase(rhs.getName()); + } + }); + } +} From 7329618cc210ffb3eb6c7ab3f26bcd9608e1ea02 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:08:08 +0200 Subject: [PATCH 041/119] Fragment added to list the VPN profiles --- .../res/layout/profile_list_fragment.xml | 38 +++++++++ src/frontends/android/res/values/strings.xml | 4 +- .../android/ui/VpnProfileListFragment.java | 77 +++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/res/layout/profile_list_fragment.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java diff --git a/src/frontends/android/res/layout/profile_list_fragment.xml b/src/frontends/android/res/layout/profile_list_fragment.xml new file mode 100644 index 000000000..50d628bfa --- /dev/null +++ b/src/frontends/android/res/layout/profile_list_fragment.xml @@ -0,0 +1,38 @@ + + + + + + + + + diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 83c09a6a1..5b7acfc51 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -21,7 +21,9 @@ strongSwan VPN Client Hello World, strongSwanActivity! + + No VPN profiles. Gateway: Username: - \ No newline at end of file + diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java new file mode 100644 index 000000000..6b1f4192f --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import java.util.List; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.ui.adapter.VpnProfileAdapter; + +import android.app.Fragment; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +public class VpnProfileListFragment extends Fragment +{ + private List mVpnProfiles; + private VpnProfileDataSource mDataSource; + private VpnProfileAdapter mListAdapter; + private ListView mListView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.profile_list_fragment, null); + + mListView = (ListView)view.findViewById(R.id.profile_list); + mListView.setEmptyView(view.findViewById(R.id.profile_list_empty)); + mListView.setAdapter(mListAdapter); + + return view; + } + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + Context context = getActivity().getApplicationContext(); + + mDataSource = new VpnProfileDataSource(this.getActivity()); + mDataSource.open(); + + /* cached list of profiles used as backend for the ListView */ + mVpnProfiles = mDataSource.getAllVpnProfiles(); + + mListAdapter = new VpnProfileAdapter(context, R.layout.profile_list_item, mVpnProfiles); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + mDataSource.close(); + } +} From 0458ac7cbcc1cfbf298bdd4d8431425f6df7bcd2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:12:56 +0200 Subject: [PATCH 042/119] Show list fragment in main activity --- src/frontends/android/res/layout/main.xml | 25 ++++++++++++++++---- src/frontends/android/res/values/strings.xml | 1 - 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/frontends/android/res/layout/main.xml b/src/frontends/android/res/layout/main.xml index bc12cd823..104a26d03 100644 --- a/src/frontends/android/res/layout/main.xml +++ b/src/frontends/android/res/layout/main.xml @@ -1,12 +1,27 @@ + - + - \ No newline at end of file + diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 5b7acfc51..99859d156 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -19,7 +19,6 @@ strongSwan VPN Client - Hello World, strongSwanActivity! No VPN profiles. From 56a922b2ed48f8ea3aa6e92839834603df0c905c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:23:21 +0200 Subject: [PATCH 043/119] Added an activity to edit basic VPN profile details Already load existing data based on extra data delivered with the Intent, no saving and CA certificate handling yet. --- src/frontends/android/AndroidManifest.xml | 5 + .../res/layout/profile_detail_view.xml | 84 ++++++++++++++ src/frontends/android/res/values/strings.xml | 7 ++ .../android/ui/VpnProfileDetailActivity.java | 106 ++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 src/frontends/android/res/layout/profile_detail_view.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 3a1008218..a88bca533 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -22,6 +22,11 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 99859d156..b81af924a 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -22,7 +22,14 @@ No VPN profiles. + Add VPN profile + + + Profile Name: + (use gateway address) Gateway: Username: + Password: + (prompt when needed) diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java new file mode 100644 index 000000000..0a8dc1687 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; + +public class VpnProfileDetailActivity extends Activity +{ + private VpnProfileDataSource mDataSource; + private Long mId; + private VpnProfile mProfile; + private EditText mName; + private EditText mGateway; + private EditText mUsername; + private EditText mPassword; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + /* the title is set when we load the profile, if any */ + getActionBar().setDisplayHomeAsUpEnabled(true); + + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + + setContentView(R.layout.profile_detail_view); + + mName = (EditText)findViewById(R.id.name); + mPassword = (EditText)findViewById(R.id.password); + mGateway = (EditText)findViewById(R.id.gateway); + mUsername = (EditText)findViewById(R.id.username); + + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); + if (mId == null) + { + Bundle extras = getIntent().getExtras(); + mId = extras == null ? null : extras.getLong(VpnProfileDataSource.KEY_ID); + } + + loadProfileData(); + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + mDataSource.close(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putLong(VpnProfileDataSource.KEY_ID, mId); + } + + /** + * Load an existing profile if we got an ID + */ + private void loadProfileData() + { + getActionBar().setTitle(R.string.add_profile); + if (mId != null) + { + mProfile = mDataSource.getVpnProfile(mId); + if (mProfile != null) + { + mName.setText(mProfile.getName()); + mGateway.setText(mProfile.getGateway()); + mUsername.setText(mProfile.getUsername()); + mPassword.setText(mProfile.getPassword()); + getActionBar().setTitle(mProfile.getName()); + } + else + { + Log.e(VpnProfileDetailActivity.class.getSimpleName(), + "VPN profile with id " + mId + " not found"); + finish(); + } + } + } +} From c2e427c287731b7ee70ed6ce060d1d1523062b1b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:40:03 +0200 Subject: [PATCH 044/119] The list fragment uses a menu to provide an option to add new VPN profiles --- .../android/res/menu/profile_list.xml | 22 ++++++++ .../strongswan/android/ui/MainActivity.java | 21 ++++++++ .../android/ui/VpnProfileListFragment.java | 51 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/frontends/android/res/menu/profile_list.xml diff --git a/src/frontends/android/res/menu/profile_list.xml b/src/frontends/android/res/menu/profile_list.xml new file mode 100644 index 000000000..57c9a86a4 --- /dev/null +++ b/src/frontends/android/res/menu/profile_list.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index ae3a5e3ef..1db68274e 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -1,5 +1,23 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + package org.strongswan.android.ui; +import android.app.ActionBar; import android.app.Activity; import android.content.Intent; import android.net.VpnService; @@ -13,6 +31,9 @@ public class MainActivity extends Activity super.onCreate(savedInstanceState); setContentView(R.layout.main); startVpnService(); + + ActionBar bar = getActionBar(); + bar.setDisplayShowTitleEnabled(false); } private void startVpnService() diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java index 6b1f4192f..a7211c5de 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java @@ -24,16 +24,23 @@ import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.ui.adapter.VpnProfileAdapter; +import android.app.Activity; import android.app.Fragment; import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; public class VpnProfileListFragment extends Fragment { + private static final int ADD_REQUEST = 1; + private List mVpnProfiles; private VpnProfileDataSource mDataSource; private VpnProfileAdapter mListAdapter; @@ -56,6 +63,7 @@ public class VpnProfileListFragment extends Fragment public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setHasOptionsMenu(true); Context context = getActivity().getApplicationContext(); @@ -74,4 +82,47 @@ public class VpnProfileListFragment extends Fragment super.onDestroy(); mDataSource.close(); } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) + { + inflater.inflate(R.menu.profile_list, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.add_profile: + Intent connectionIntent = new Intent(getActivity(), + VpnProfileDetailActivity.class); + startActivityForResult(connectionIntent, ADD_REQUEST); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) + { + switch (requestCode) + { + case ADD_REQUEST: + if (resultCode != Activity.RESULT_OK) + { + return; + } + long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0); + VpnProfile profile = mDataSource.getVpnProfile(id); + if (profile != null) + { + mVpnProfiles.add(profile); + mListAdapter.notifyDataSetChanged(); + } + return; + } + super.onActivityResult(requestCode, resultCode, data); + } } From a3e2f127dcda5e8fcd14f5a7b2f41dd7d7c1a454 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:45:23 +0200 Subject: [PATCH 045/119] Provide a menu with options to save VPN profiles The ID of the updated/inserted profile is sent back to the activity that started the detail view. --- .../android/res/menu/profile_edit.xml | 28 ++++++ src/frontends/android/res/values/strings.xml | 5 ++ .../android/ui/VpnProfileDetailActivity.java | 90 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 src/frontends/android/res/menu/profile_edit.xml diff --git a/src/frontends/android/res/menu/profile_edit.xml b/src/frontends/android/res/menu/profile_edit.xml new file mode 100644 index 000000000..e69020ed0 --- /dev/null +++ b/src/frontends/android/res/menu/profile_edit.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index b81af924a..9d52c1e3e 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -25,11 +25,16 @@ Add VPN profile + Save + Cancel Profile Name: (use gateway address) Gateway: Username: Password: (prompt when needed) + + Please enter the gateway address here + Please enter your username here diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 0a8dc1687..56eef15ac 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -22,8 +22,13 @@ import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.Window; import android.widget.EditText; public class VpnProfileDetailActivity extends Activity @@ -78,6 +83,91 @@ public class VpnProfileDetailActivity extends Activity outState.putLong(VpnProfileDataSource.KEY_ID, mId); } + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.profile_edit, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + case R.id.menu_cancel: + finish(); + return true; + case R.id.menu_accept: + saveProfile(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * Save or update the profile depending on whether we actually have a + * profile object or not (this was created in updateProfileData) + */ + private void saveProfile() + { + if (verifyInput()) + { + if (mProfile != null) + { + updateProfileData(); + mDataSource.updateVpnProfile(mProfile); + } + else + { + mProfile = new VpnProfile(); + updateProfileData(); + mDataSource.insertProfile(mProfile); + } + setResult(RESULT_OK, new Intent().putExtra(VpnProfileDataSource.KEY_ID, mProfile.getId())); + finish(); + } + } + + /** + * Verify the user input and display error messages. + * @return true if the input is valid + */ + private boolean verifyInput() + { + boolean valid = true; + if (mGateway.getText().toString().trim().isEmpty()) + { + mGateway.setError(getString(R.string.alert_text_no_input_gateway)); + valid = false; + } + if (mUsername.getText().toString().trim().isEmpty()) + { + mUsername.setError(getString(R.string.alert_text_no_input_username)); + valid = false; + } + return valid; + } + + /** + * Update the profile object with the data entered by the user + */ + private void updateProfileData() + { + /* the name is optional, we default to the gateway if none is given */ + String name = mName.getText().toString().trim(); + String gateway = mGateway.getText().toString().trim(); + mProfile.setName(name.isEmpty() ? gateway : name); + mProfile.setGateway(gateway); + mProfile.setUsername(mUsername.getText().toString().trim()); + String password = mPassword.getText().toString().trim(); + password = password.isEmpty() ? null : password; + mProfile.setPassword(password); + } + /** * Load an existing profile if we got an ID */ From c6b736b9f5409371a69298aaab2a7f99d06b7ddd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:49:42 +0200 Subject: [PATCH 046/119] Use a contextual action bar to edit and delete selected VPN profiles --- .../android/res/menu/profile_list_context.xml | 24 ++++ src/frontends/android/res/values/strings.xml | 6 + .../strongswan/android/data/VpnProfile.java | 10 ++ .../android/ui/VpnProfileListFragment.java | 107 +++++++++++++++++- 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/res/menu/profile_list_context.xml diff --git a/src/frontends/android/res/menu/profile_list_context.xml b/src/frontends/android/res/menu/profile_list_context.xml new file mode 100644 index 000000000..e674ae856 --- /dev/null +++ b/src/frontends/android/res/menu/profile_list_context.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 9d52c1e3e..53a94bb30 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -23,6 +23,12 @@ No VPN profiles. Add VPN profile + Edit + Delete + Selected profiles deleted + No profile selected + One profile selected + %1$d profiles selected" Save diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java index 8dc5f2499..3e248d2eb 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -87,4 +87,14 @@ public class VpnProfile { return mName; } + + @Override + public boolean equals(Object o) + { + if (o != null && o instanceof VpnProfile) + { + return this.mId == ((VpnProfile)o).getId(); + } + return false; + } } diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java index a7211c5de..252330b28 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java @@ -17,6 +17,8 @@ package org.strongswan.android.ui; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import org.strongswan.android.R; @@ -29,17 +31,23 @@ import android.app.Fragment; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.view.ActionMode; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; +import android.widget.Toast; public class VpnProfileListFragment extends Fragment { private static final int ADD_REQUEST = 1; + private static final int EDIT_REQUEST = 2; private List mVpnProfiles; private VpnProfileDataSource mDataSource; @@ -54,6 +62,8 @@ public class VpnProfileListFragment extends Fragment mListView = (ListView)view.findViewById(R.id.profile_list); mListView.setEmptyView(view.findViewById(R.id.profile_list_empty)); + mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mListView.setMultiChoiceModeListener(mVpnProfileSelected); mListView.setAdapter(mListAdapter); return view; @@ -110,6 +120,7 @@ public class VpnProfileListFragment extends Fragment switch (requestCode) { case ADD_REQUEST: + case EDIT_REQUEST: if (resultCode != Activity.RESULT_OK) { return; @@ -117,7 +128,8 @@ public class VpnProfileListFragment extends Fragment long id = data.getLongExtra(VpnProfileDataSource.KEY_ID, 0); VpnProfile profile = mDataSource.getVpnProfile(id); if (profile != null) - { + { /* in case this was an edit, we remove it first */ + mVpnProfiles.remove(profile); mVpnProfiles.add(profile); mListAdapter.notifyDataSetChanged(); } @@ -125,4 +137,97 @@ public class VpnProfileListFragment extends Fragment } super.onActivityResult(requestCode, resultCode, data); } + + private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() { + private HashSet mSelected; + private MenuItem mEditProfile; + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) + { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) + { + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) + { + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.profile_list_context, menu); + mEditProfile = menu.findItem(R.id.edit_profile); + mSelected = new HashSet(); + mode.setTitle("Select Profiles"); + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) + { + switch (item.getItemId()) + { + case R.id.edit_profile: + { + int position = mSelected.iterator().next(); + VpnProfile profile = (VpnProfile)mListView.getItemAtPosition(position); + Intent connectionIntent = new Intent(getActivity(), VpnProfileDetailActivity.class); + connectionIntent.putExtra(VpnProfileDataSource.KEY_ID, profile.getId()); + startActivityForResult(connectionIntent, EDIT_REQUEST); + break; + } + case R.id.delete_profile: + { + ArrayList profiles = new ArrayList(); + for (int position : mSelected) + { + profiles.add((VpnProfile)mListView.getItemAtPosition(position)); + } + for (VpnProfile profile : profiles) + { + mDataSource.deleteVpnProfile(profile); + mVpnProfiles.remove(profile); + } + mListAdapter.notifyDataSetChanged(); + Toast.makeText(VpnProfileListFragment.this.getActivity(), + R.string.profiles_deleted, Toast.LENGTH_SHORT).show(); + break; + } + default: + return false; + } + mode.finish(); + return true; + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) + { + if (checked) + { + mSelected.add(position); + } + else + { + mSelected.remove(position); + } + final int checkedCount = mSelected.size(); + mEditProfile.setEnabled(checkedCount == 1); + switch (checkedCount) + { + case 0: + mode.setSubtitle(R.string.no_profile_selected); + break; + case 1: + mode.setSubtitle(R.string.one_profile_selected); + break; + default: + mode.setSubtitle(String.format(getString(R.string.x_profiles_selected), checkedCount)); + break; + } + } + }; } From da9bb5044f9b1f5a1bcf759381ac8303db59d11e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 19:57:51 +0200 Subject: [PATCH 047/119] Make click events on the profile list available to the Activity If the Activity this fragment is placed in implements the provided interface it is notified about clicks on any of the profiles. --- .../android/ui/VpnProfileListFragment.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java index 252330b28..be0a9004e 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java @@ -53,6 +53,14 @@ public class VpnProfileListFragment extends Fragment private VpnProfileDataSource mDataSource; private VpnProfileAdapter mListAdapter; private ListView mListView; + private OnVpnProfileSelectedListener mListener; + + /** + * The activity containing this fragment should implement this interface + */ + public interface OnVpnProfileSelectedListener { + public void onVpnProfileSelected(VpnProfile profile); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -62,6 +70,7 @@ public class VpnProfileListFragment extends Fragment mListView = (ListView)view.findViewById(R.id.profile_list); mListView.setEmptyView(view.findViewById(R.id.profile_list_empty)); + mListView.setOnItemClickListener(mVpnProfileClicked); mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); mListView.setMultiChoiceModeListener(mVpnProfileSelected); mListView.setAdapter(mListAdapter); @@ -93,6 +102,17 @@ public class VpnProfileListFragment extends Fragment mDataSource.close(); } + @Override + public void onAttach(Activity activity) + { + super.onAttach(activity); + + if (activity instanceof OnVpnProfileSelectedListener) + { + mListener = (OnVpnProfileSelectedListener)activity; + } + } + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { @@ -138,6 +158,17 @@ public class VpnProfileListFragment extends Fragment super.onActivityResult(requestCode, resultCode, data); } + private final OnItemClickListener mVpnProfileClicked = new OnItemClickListener() { + @Override + public void onItemClick(AdapterView a, View v, int position, long id) + { + if (mListener != null) + { + mListener.onVpnProfileSelected((VpnProfile)a.getItemAtPosition(position)); + } + } + }; + private final MultiChoiceModeListener mVpnProfileSelected = new MultiChoiceModeListener() { private HashSet mSelected; private MenuItem mEditProfile; From 3a32ba7111c07b1bf41024fa0c4a996a8f6d47eb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 20:03:23 +0200 Subject: [PATCH 048/119] Use Holo as theme --- src/frontends/android/AndroidManifest.xml | 3 ++- src/frontends/android/res/values/styles.xml | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/res/values/styles.xml diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index a88bca533..2943e0178 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -10,7 +10,8 @@ + android:label="@string/app_name" + android:theme="@style/ApplicationTheme" > + + + + + + From 40dfe8f1d86c869be0ab951537b2e384f3ce8766 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 17 Jul 2012 20:03:40 +0200 Subject: [PATCH 049/119] Remove restriction to portrait orientation --- src/frontends/android/AndroidManifest.xml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 2943e0178..ac6800d16 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -15,11 +15,9 @@ + android:launchMode="singleTop" > - From a305419b40b63ce2e2781c7215d660a96a0a1069 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 18 Jul 2012 13:40:29 +0200 Subject: [PATCH 050/119] Trusted CA certificates are loaded and cached by a static singleton --- .../logic/TrustedCertificateManager.java | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java diff --git a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java new file mode 100644 index 000000000..04a292a00 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.logic; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import android.util.Log; + +public class TrustedCertificateManager +{ + private static final String TAG = TrustedCertificateManager.class.getSimpleName(); + private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); + private Hashtable mCACerts = new Hashtable(); + private boolean mLoaded; + + /** + * Private constructor to prevent instantiation from other classes. + */ + private TrustedCertificateManager() + { + } + + /** + * This is not instantiated until the first call to getInstance() + */ + private static class Singleton { + public static final TrustedCertificateManager mInstance = new TrustedCertificateManager(); + } + + /** + * Get the single instance of the CA certificate manager. + * @return CA certificate manager + */ + public static TrustedCertificateManager getInstance() + { + return Singleton.mInstance; + } + + /** + * Forces a load/reload of the cached CA certificates. + * As this takes a while it should be called asynchronously. + * @return reference to itself + */ + public TrustedCertificateManager reload() + { + Log.d(TAG, "Force reload of cached CA certificates"); + this.mLock.writeLock().lock(); + loadCertificates(); + this.mLock.writeLock().unlock(); + return this; + } + + /** + * Ensures that the certificates are loaded but does not force a reload. + * As this takes a while if the certificates are not loaded yet it should + * be called asynchronously. + * @return reference to itself + */ + public TrustedCertificateManager load() + { + Log.d(TAG, "Ensure cached CA certificates are loaded"); + this.mLock.writeLock().lock(); + if (!this.mLoaded) + { + loadCertificates(); + } + this.mLock.writeLock().unlock(); + return this; + } + + /** + * Opens the CA certificate KeyStore and loads the cached certificates. + * The lock must be locked when calling this method. + */ + private void loadCertificates() + { + Log.d(TAG, "Load cached CA certificates"); + try + { + KeyStore store = KeyStore.getInstance("AndroidCAStore"); + store.load(null, null); + this.mCACerts = fetchCertificates(store); + this.mLoaded = true; + Log.d(TAG, "Cached CA certificates loaded"); + } + catch (Exception ex) + { + ex.printStackTrace(); + this.mCACerts = new Hashtable(); + } + } + + /** + * Load all X.509 certificates from the given KeyStore. + * @param store KeyStore to load certificates from + * @return Hashtable mapping aliases to certificates + */ + private Hashtable fetchCertificates(KeyStore store) + { + Hashtable certs = new Hashtable(); + try + { + Enumeration aliases = store.aliases(); + while (aliases.hasMoreElements()) + { + String alias = aliases.nextElement(); + Certificate cert; + cert = store.getCertificate(alias); + if (cert != null && cert instanceof X509Certificate) + { + certs.put(alias, (X509Certificate)cert); + } + } + } + catch (KeyStoreException ex) + { + ex.printStackTrace(); + } + return certs; + } + + /** + * Retrieve the CA certificate with the given alias. + * @param alias alias of the certificate to get + * @return the certificate, null if not found + */ + public X509Certificate getCACertificateFromAlias(String alias) + { + this.mLock.readLock().lock(); + X509Certificate certificate = this.mCACerts.get(alias); + this.mLock.readLock().unlock(); + return certificate; + } + + /** + * Get all CA certificates (from the system and user keystore). + * @return Hashtable mapping aliases to certificates + */ + @SuppressWarnings("unchecked") + public Hashtable getAllCACertificates() + { + Hashtable certs; + this.mLock.readLock().lock(); + certs = (Hashtable)this.mCACerts.clone(); + this.mLock.readLock().unlock(); + return certs; + } + + /** + * Get only the CA certificates installed by the user. + * @return Hashtable mapping aliases to certificates + */ + public Hashtable getUserCACertificates() + { + Hashtable certs = new Hashtable(); + this.mLock.readLock().lock(); + for (String alias : this.mCACerts.keySet()) + { + if (alias.startsWith("user:")) + { + certs.put(alias, this.mCACerts.get(alias)); + } + } + this.mLock.readLock().unlock(); + return certs; + } +} From 5afb1e3c4521990e3cffc33f7a7371cf32967c62 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 18 Jul 2012 13:43:34 +0200 Subject: [PATCH 051/119] Initially load CA certificates when the main Activity is created --- .../src/org/strongswan/android/ui/MainActivity.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 1db68274e..d2a7eecc7 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -17,6 +17,8 @@ package org.strongswan.android.ui; +import org.strongswan.android.logic.TrustedCertificateManager; + import android.app.ActionBar; import android.app.Activity; import android.content.Intent; @@ -34,6 +36,15 @@ public class MainActivity extends Activity ActionBar bar = getActionBar(); bar.setDisplayShowTitleEnabled(false); + + /* load CA certificates in a background thread */ + new Thread(new Runnable() { + @Override + public void run() + { + TrustedCertificateManager.getInstance().load(); + } + }).start(); } private void startVpnService() From 95e9a12c2898f74486275b1031dc70dc2cb2f998 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 13:34:44 +0200 Subject: [PATCH 052/119] Don't attach to actual Java threads (or already attached ones) We check this by trying to retrieve a JNIEnv object from the JVM, if one is returned the current thread is not native (created from Java) or the thread is already attached. --- .../android/jni/libandroidbridge/android_jni.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c index 0acebbbcd..b5e935a57 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.c +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -46,6 +46,11 @@ static void attached_thread_cleanup(void *arg) */ void androidjni_attach_thread(JNIEnv **env) { + if ((*android_jvm)->GetEnv(android_jvm, (void**)env, + JNI_VERSION_1_6) == JNI_OK) + { /* already attached or even a Java thread */ + return; + } (*android_jvm)->AttachCurrentThread(android_jvm, env, NULL); /* use a thread-local value with a destructor that automatically detaches * the thread from the JVM before it terminates, if not done manually */ @@ -57,8 +62,11 @@ void androidjni_attach_thread(JNIEnv **env) */ void androidjni_detach_thread() { - androidjni_threadlocal->set(androidjni_threadlocal, NULL); - (*android_jvm)->DetachCurrentThread(android_jvm); + if (androidjni_threadlocal->get(androidjni_threadlocal)) + { /* only do this if we actually attached this thread */ + androidjni_threadlocal->set(androidjni_threadlocal, NULL); + (*android_jvm)->DetachCurrentThread(android_jvm); + } } /** @@ -85,7 +93,8 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) } /** - * Called when this library is unloaded by the JVM + * Called when this library is unloaded by the JVM (which never happens on + * Android) */ void JNI_OnUnload(JavaVM *vm, void *reserved) { From 19567a5e3a776ba65cec7eaf375853e07d868f81 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 13:40:47 +0200 Subject: [PATCH 053/119] Helper function added to handle Java exceptions in native code --- .../android/jni/libandroidbridge/android_jni.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 77c33f73e..427c641b4 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -24,6 +24,7 @@ #define ANDROID_JNI_H_ #include +#include #define JNI_PACKAGE org_strongswan_android_logic #define JNI_PACKAGE_STRING "org/strongswan/android/logic" @@ -62,4 +63,21 @@ void androidjni_attach_thread(JNIEnv **env); */ void androidjni_detach_thread(); +/** + * Handle exceptions thrown by a JNI call + * + * @param env JNIEnv + * @return TRUE if an exception was thrown + */ +static inline bool androidjni_exception_occurred(JNIEnv *env) +{ + if ((*env)->ExceptionOccurred(env)) + { /* clear any exception, otherwise the VM is terminated */ + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return TRUE; + } + return FALSE; +} + #endif /** ANDROID_JNI_H_ @}*/ From 9756cf22f21c3e3e8bcf1f63df4e14070bd0e6c2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 14:00:16 +0200 Subject: [PATCH 054/119] Show progress bar in ActionBar while loading cached CA certificates --- .../src/org/strongswan/android/ui/MainActivity.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index d2a7eecc7..063d7a961 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -24,6 +24,7 @@ import android.app.Activity; import android.content.Intent; import android.net.VpnService; import android.os.Bundle; +import android.view.Window; public class MainActivity extends Activity { @@ -31,6 +32,7 @@ public class MainActivity extends Activity public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.main); startVpnService(); @@ -38,11 +40,19 @@ public class MainActivity extends Activity bar.setDisplayShowTitleEnabled(false); /* load CA certificates in a background thread */ + setProgressBarIndeterminateVisibility(true); new Thread(new Runnable() { @Override public void run() { TrustedCertificateManager.getInstance().load(); + runOnUiThread(new Runnable() { + @Override + public void run() + { + setProgressBarIndeterminateVisibility(false); + } + }); } }).start(); } From c8b942a1e218d0984c02b266994a6ef6855f9fdb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 14:02:38 +0200 Subject: [PATCH 055/119] Menu option added to reload cached CA certificates This might be required if the user installs a new CA certificate. --- src/frontends/android/res/menu/main.xml | 23 +++++++++++ src/frontends/android/res/values/strings.xml | 1 + .../strongswan/android/ui/MainActivity.java | 41 ++++++++++++++++++- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/res/menu/main.xml diff --git a/src/frontends/android/res/menu/main.xml b/src/frontends/android/res/menu/main.xml new file mode 100644 index 000000000..f5d1e31cf --- /dev/null +++ b/src/frontends/android/res/menu/main.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 53a94bb30..761b16a69 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -19,6 +19,7 @@ strongSwan VPN Client + Reload CA certificates No VPN profiles. diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 063d7a961..6a2988904 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -17,6 +17,7 @@ package org.strongswan.android.ui; +import org.strongswan.android.R; import org.strongswan.android.logic.TrustedCertificateManager; import android.app.ActionBar; @@ -24,6 +25,9 @@ import android.app.Activity; import android.content.Intent; import android.net.VpnService; import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.Window; public class MainActivity extends Activity @@ -57,7 +61,42 @@ public class MainActivity extends Activity }).start(); } - private void startVpnService() + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case R.id.menu_reload_certs: + setProgressBarIndeterminateVisibility(true); + new Thread(new Runnable() { + @Override + public void run() + { + TrustedCertificateManager.getInstance().reload(); + runOnUiThread(new Runnable() { + @Override + public void run() + { + setProgressBarIndeterminateVisibility(false); + } + }); + } + }).start(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + protected void prepareVpnService() { Intent intent = VpnService.prepare(this); if (intent != null) From 0b362ed8377505a9aba3030b70999b587936edef Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 14:11:27 +0200 Subject: [PATCH 056/119] MainActivity starts CharonVpnService if a VpnProfile is clicked in the list This is done by implementing the OnVpnProfileSelectedListener interface provided by VpnProfileListFragment. --- .../strongswan/android/ui/MainActivity.java | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 6a2988904..65131fa45 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -18,7 +18,11 @@ package org.strongswan.android.ui; import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.CharonVpnService; import org.strongswan.android.logic.TrustedCertificateManager; +import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedListener; import android.app.ActionBar; import android.app.Activity; @@ -30,15 +34,17 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.Window; -public class MainActivity extends Activity +public class MainActivity extends Activity implements OnVpnProfileSelectedListener { + private static final int PREPARE_VPN_SERVICE = 0; + private VpnProfile activeProfile; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.main); - startVpnService(); ActionBar bar = getActionBar(); bar.setDisplayShowTitleEnabled(false); @@ -96,26 +102,45 @@ public class MainActivity extends Activity } } + /** + * Prepare the VpnService. If this succeeds the current VPN profile is + * started. + */ protected void prepareVpnService() { Intent intent = VpnService.prepare(this); if (intent != null) { - startActivityForResult(intent, 0); + startActivityForResult(intent, PREPARE_VPN_SERVICE); } else { - onActivityResult(0, RESULT_OK, null); + onActivityResult(PREPARE_VPN_SERVICE, RESULT_OK, null); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == RESULT_OK) + switch (requestCode) { - Intent intent = new Intent(this, CharonVpnService.class); - startService(intent); + case PREPARE_VPN_SERVICE: + if (resultCode == RESULT_OK && activeProfile != null) + { + Intent intent = new Intent(this, CharonVpnService.class); + intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId()); + this.startService(intent); + } + break; + default: + super.onActivityResult(requestCode, resultCode, data); } } + + @Override + public void onVpnProfileSelected(VpnProfile profile) + { + activeProfile = profile; + prepareVpnService(); + } } From 6316b50280e4b00a60f14cf706d52ed7b3d8acd8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 15:17:45 +0200 Subject: [PATCH 057/119] Renamed main Activity (shorter name in Launcher) --- src/frontends/android/AndroidManifest.xml | 7 +++---- src/frontends/android/res/values/strings.xml | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index ac6800d16..7dcbf010c 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -14,7 +14,7 @@ android:theme="@style/ApplicationTheme" > @@ -22,8 +22,7 @@ + android:name=".ui.VpnProfileDetailActivity" > - \ No newline at end of file + diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 761b16a69..8c171db65 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -19,6 +19,7 @@ strongSwan VPN Client + strongSwan Reload CA certificates From d200749424f8c7522cd29a6be5e39cbc4179740d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 15:25:06 +0200 Subject: [PATCH 058/119] Set default log level in libandroidbridge --- .../android/jni/libandroidbridge/charonservice.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index c86554981..bbd5f5891 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -26,6 +26,8 @@ #include #include +#define ANDROID_DEBUG_LEVEL 1 + typedef struct private_charonservice_t private_charonservice_t; /** @@ -56,10 +58,11 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) { va_list args; - if (level <= 4) + if (level <= ANDROID_DEBUG_LEVEL) { char sgroup[16], buffer[8192]; char *current = buffer, *next; + snprintf(sgroup, sizeof(sgroup), "%N", debug_names, group); va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); @@ -90,6 +93,9 @@ static void charonservice_init() }, ); charonservice = &this->public; + + lib->settings->set_int(lib->settings, + "charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL); } /** From a304874319225eabbb8bae3aa96004f384732ccc Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 15:30:49 +0200 Subject: [PATCH 059/119] Add signal handler for fatal signals to libandroidbridge --- .../jni/libandroidbridge/charonservice.c | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index bbd5f5891..671561f5f 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -15,6 +15,7 @@ * for more details. */ +#include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include #define ANDROID_DEBUG_LEVEL 1 @@ -109,11 +111,23 @@ static void charonservice_deinit() charonservice = NULL; } +/** + * Handle SIGSEGV/SIGILL signals raised by threads + */ +static void segv_handler(int signal) +{ + dbg_android(DBG_DMN, 1, "thread %u received %d", thread_current_id(), + signal); + exit(1); +} + /** * Initialize charon and the libraries via JNI */ JNI_METHOD(CharonVpnService, initializeCharon, void) { + struct sigaction action; + /* logging for library during initialization, as we have no bus yet */ dbg = dbg_android; @@ -152,6 +166,16 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) return; } + /* add handler for SEGV and ILL etc. */ + action.sa_handler = segv_handler; + action.sa_flags = 0; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGILL, &action, NULL); + sigaction(SIGBUS, &action, NULL); + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); + /* start daemon (i.e. the threads in the thread-pool) */ charon->start(charon); } From 529c8c88a36ec2251ac32edcee0710838dece2bb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 15:51:00 +0200 Subject: [PATCH 060/119] Keep a global reference to the CharonVpnService object in charonservice --- .../jni/libandroidbridge/charonservice.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 671561f5f..75d0da693 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -41,6 +41,11 @@ struct private_charonservice_t { * public interface */ charonservice_t public; + + /** + * CharonVpnService reference + */ + jobject vpn_service; }; /** @@ -86,13 +91,14 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) /** * Initialize the charonservice object */ -static void charonservice_init() +static void charonservice_init(JNIEnv *env, jobject service) { private_charonservice_t *this; INIT(this, .public = { }, + .vpn_service = (*env)->NewGlobalRef(env, service), ); charonservice = &this->public; @@ -103,10 +109,11 @@ static void charonservice_init() /** * Deinitialize the charonservice object */ -static void charonservice_deinit() +static void charonservice_deinit(JNIEnv *env) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + (*env)->DeleteGlobalRef(env, this->vpn_service); free(this); charonservice = NULL; } @@ -153,13 +160,13 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) return; } - charonservice_init(); + charonservice_init(env, this); if (!libcharon_init("charon") || !charon->initialize(charon, PLUGINS)) { libcharon_deinit(); - charonservice_deinit(); + charonservice_deinit(env); libipsec_deinit(); libhydra_deinit(); library_deinit(); @@ -186,7 +193,7 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) JNI_METHOD(CharonVpnService, deinitializeCharon, void) { libcharon_deinit(); - charonservice_deinit(); + charonservice_deinit(env); libipsec_deinit(); libhydra_deinit(); library_deinit(); From b21979f12f725a87f250c7c9f12d22e705e9e5e9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 17:07:44 +0200 Subject: [PATCH 061/119] Added simple adapter for trusted certificates (to be used with a Spinner widget) --- .../res/layout/trusted_certificates_item.xml | 29 ++++ .../ui/adapter/TrustedCertificateAdapter.java | 149 ++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 src/frontends/android/res/layout/trusted_certificates_item.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java diff --git a/src/frontends/android/res/layout/trusted_certificates_item.xml b/src/frontends/android/res/layout/trusted_certificates_item.xml new file mode 100644 index 000000000..48d77757d --- /dev/null +++ b/src/frontends/android/res/layout/trusted_certificates_item.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java b/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java new file mode 100644 index 000000000..ae94adc52 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/adapter/TrustedCertificateAdapter.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui.adapter; + +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Hashtable; +import java.util.Map.Entry; + +import org.strongswan.android.R; + +import android.content.Context; +import android.net.http.SslCertificate; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +public class TrustedCertificateAdapter extends BaseAdapter +{ + private final ArrayList mContent; + private final Context mContext; + + public class CertEntry implements Comparable + { + public X509Certificate mCert; + public String mAlias; + public String mDisplayName; + + public CertEntry(String alias, X509Certificate cert) + { + mCert = cert; + mAlias = alias; + } + + public String getDisplayText() + { + if (mDisplayName == null) + { + SslCertificate cert = new SslCertificate(mCert); + String o = cert.getIssuedTo().getOName(); + String ou = cert.getIssuedTo().getUName(); + String cn = cert.getIssuedTo().getCName(); + if (!o.isEmpty()) + { + mDisplayName = o; + if (!cn.isEmpty()) + { + mDisplayName = mDisplayName + ", " + cn; + } + else if (!ou.isEmpty()) + { + mDisplayName = mDisplayName + ", " + ou; + } + } + else if (!cn.isEmpty()) + { + mDisplayName = cn; + } + else + { + mDisplayName = cert.getIssuedTo().getDName(); + } + } + return mDisplayName; + } + + @Override + public int compareTo(CertEntry another) + { + return getDisplayText().compareToIgnoreCase(another.getDisplayText()); + } + } + + public TrustedCertificateAdapter(Context context, + Hashtable content) + { + mContext = context; + mContent = new ArrayList(); + for (Entry entry : content.entrySet()) + { + mContent.add(new CertEntry(entry.getKey(), entry.getValue())); + } + Collections.sort(mContent); + } + + @Override + public int getCount() + { + return mContent.size(); + } + + @Override + public Object getItem(int position) + { + return mContent.get(position); + } + + /** + * Returns the position (index) of the entry with the given alias. + * + * @param alias alias of the item to find + * @return the position (index) in the list + */ + public int getItemPosition(String alias) + { + for (int i = 0; i < mContent.size(); i++) + { + if (mContent.get(i).mAlias.equals(alias)) + { + return i; + } + } + return -1; + } + + @Override + public long getItemId(int position) + { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + LayoutInflater inflater = LayoutInflater.from(mContext); + final View certView = inflater.inflate(R.layout.trusted_certificates_item, null); + final TextView certText = (TextView)certView.findViewById(R.id.certificate_name); + certText.setText(mContent.get(position).getDisplayText()); + return certView; + } +} From 8db37772f5fe4dec82a8b176f5ca6de3a043a277 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 17:52:10 +0200 Subject: [PATCH 062/119] Simplified asynchronous loading of CA certificates in MainActivity --- .../strongswan/android/ui/MainActivity.java | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 65131fa45..35f8c653f 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -28,6 +28,7 @@ import android.app.ActionBar; import android.app.Activity; import android.content.Intent; import android.net.VpnService; +import android.os.AsyncTask; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; @@ -49,22 +50,8 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen ActionBar bar = getActionBar(); bar.setDisplayShowTitleEnabled(false); - /* load CA certificates in a background thread */ - setProgressBarIndeterminateVisibility(true); - new Thread(new Runnable() { - @Override - public void run() - { - TrustedCertificateManager.getInstance().load(); - runOnUiThread(new Runnable() { - @Override - public void run() - { - setProgressBarIndeterminateVisibility(false); - } - }); - } - }).start(); + /* load CA certificates in a background task */ + new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, false); } @Override @@ -81,21 +68,7 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen switch (item.getItemId()) { case R.id.menu_reload_certs: - setProgressBarIndeterminateVisibility(true); - new Thread(new Runnable() { - @Override - public void run() - { - TrustedCertificateManager.getInstance().reload(); - runOnUiThread(new Runnable() { - @Override - public void run() - { - setProgressBarIndeterminateVisibility(false); - } - }); - } - }).start(); + new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true); return true; default: return super.onOptionsItemSelected(item); @@ -143,4 +116,30 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen activeProfile = profile; prepareVpnService(); } + + /** + * Class that loads or reloads the cached CA certificates. + */ + private class CertificateLoadTask extends AsyncTask + { + @Override + protected void onPreExecute() + { + setProgressBarIndeterminateVisibility(true); + } + @Override + protected TrustedCertificateManager doInBackground(Boolean... params) + { + if (params.length > 0 && params[0]) + { /* force a reload of the certificates */ + return TrustedCertificateManager.getInstance().reload(); + } + return TrustedCertificateManager.getInstance().load(); + } + @Override + protected void onPostExecute(TrustedCertificateManager result) + { + setProgressBarIndeterminateVisibility(false); + } + } } From fcb5448017677999d956c652b2778f6a5e2435b8 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 18:03:51 +0200 Subject: [PATCH 063/119] Allow selection of a CA certificate for a VPN profile This solution is just temporary as it really is not that user-friendly to select CA certificates with a Spinner widget. --- .../res/layout/profile_detail_view.xml | 25 +++ src/frontends/android/res/values/strings.xml | 5 + .../android/ui/VpnProfileDetailActivity.java | 179 ++++++++++++++++++ 3 files changed, 209 insertions(+) diff --git a/src/frontends/android/res/layout/profile_detail_view.xml b/src/frontends/android/res/layout/profile_detail_view.xml index 56a50655f..4952ebaa5 100644 --- a/src/frontends/android/res/layout/profile_detail_view.xml +++ b/src/frontends/android/res/layout/profile_detail_view.xml @@ -79,6 +79,31 @@ android:inputType="textPassword|textNoSuggestions" android:hint="@string/profile_password_hint" /> + + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 8c171db65..f2db4a676 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -41,8 +41,13 @@ Username: Password: (prompt when needed) + CA certificate: + Select automatically + Show all certificates Please enter the gateway address here Please enter your username here + No CA certificate selected + Please select one or activate Select automatically diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java index 56eef15ac..05ba5e8b3 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileDetailActivity.java @@ -17,34 +17,56 @@ package org.strongswan.android.ui; +import java.security.cert.X509Certificate; +import java.util.Hashtable; + import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.TrustedCertificateManager; +import org.strongswan.android.ui.adapter.TrustedCertificateAdapter; import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import android.view.Window; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; +import android.widget.Spinner; public class VpnProfileDetailActivity extends Activity { private VpnProfileDataSource mDataSource; private Long mId; private VpnProfile mProfile; + private boolean mCertsLoaded; + private String mCertAlias; + private Spinner mCertSpinner; + private TrustedCertificateAdapter.CertEntry mSelectedCert; private EditText mName; private EditText mGateway; private EditText mUsername; private EditText mPassword; + private CheckBox mCheckAll; + private CheckBox mCheckAuto; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); /* the title is set when we load the profile, if any */ getActionBar().setDisplayHomeAsUpEnabled(true); @@ -59,6 +81,45 @@ public class VpnProfileDetailActivity extends Activity mGateway = (EditText)findViewById(R.id.gateway); mUsername = (EditText)findViewById(R.id.username); + mCheckAll = (CheckBox)findViewById(R.id.ca_show_all); + mCheckAuto = (CheckBox)findViewById(R.id.ca_auto); + mCertSpinner = (Spinner)findViewById(R.id.ca_spinner); + + mCheckAuto.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + updateCertSpinner(); + } + }); + + mCheckAll.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + Hashtable certs; + certs = isChecked ? TrustedCertificateManager.getInstance().getAllCACertificates() + : TrustedCertificateManager.getInstance().getUserCACertificates(); + mCertSpinner.setAdapter(new TrustedCertificateAdapter(VpnProfileDetailActivity.this, certs)); + mSelectedCert = (TrustedCertificateAdapter.CertEntry)mCertSpinner.getSelectedItem(); + } + }); + + mCertSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, + int pos, long id) + { + mSelectedCert = (TrustedCertificateAdapter.CertEntry)parent.getSelectedItem(); + } + + @Override + public void onNothingSelected(AdapterView arg0) + { + mSelectedCert = null; + } + }); + mId = savedInstanceState == null ? null : savedInstanceState.getLong(VpnProfileDataSource.KEY_ID); if (mId == null) { @@ -67,6 +128,8 @@ public class VpnProfileDetailActivity extends Activity } loadProfileData(); + + new CertificateLoadTask().execute(); } @Override @@ -108,6 +171,111 @@ public class VpnProfileDetailActivity extends Activity } } + /** + * Show an alert in case the previously selected certificate is not found anymore + * or the user did not select a certificate in the spinner. + */ + private void showCertificateAlert() + { + AlertDialog.Builder adb = new AlertDialog.Builder(VpnProfileDetailActivity.this); + adb.setTitle(R.string.alert_text_nocertfound_title); + adb.setMessage(R.string.alert_text_nocertfound); + adb.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + dialog.cancel(); + } + }); + adb.show(); + } + + /** + * Asynchronously executed task which confirms that the certificates are loaded. + * They are loaded from the main Activity already but might not be ready yet, or + * unloaded again. + * + * Once loaded the CA certificate spinner and checkboxes are updated + * accordingly. + */ + private class CertificateLoadTask extends AsyncTask + { + @Override + protected void onPreExecute() + { + setProgressBarIndeterminateVisibility(true); + } + + @Override + protected TrustedCertificateManager doInBackground(Void... params) + { + return TrustedCertificateManager.getInstance().load(); + } + + @Override + protected void onPostExecute(TrustedCertificateManager result) + { + TrustedCertificateAdapter adapter; + if (mCertAlias != null && mCertAlias.startsWith("system:")) + { + mCheckAll.setChecked(true); + adapter = new TrustedCertificateAdapter(VpnProfileDetailActivity.this, + result.getAllCACertificates()); + } + else + { + mCheckAll.setChecked(false); + adapter = new TrustedCertificateAdapter(VpnProfileDetailActivity.this, + result.getUserCACertificates()); + } + mCertSpinner.setAdapter(adapter); + + if (mCertAlias != null) + { + int position = adapter.getItemPosition(mCertAlias); + if (position == -1) + { /* previously selected certificate is not here anymore */ + showCertificateAlert(); + } + else + { + mCertSpinner.setSelection(position); + } + } + + mSelectedCert = (TrustedCertificateAdapter.CertEntry)mCertSpinner.getSelectedItem(); + + setProgressBarIndeterminateVisibility(false); + mCertsLoaded = true; + updateCertSpinner(); + } + } + + /** + * Update the CA certificate selection UI depending on whether the + * certificate should be automatically selected or not. + */ + private void updateCertSpinner() + { + if (!mCheckAuto.isChecked()) + { + if (mCertsLoaded) + { + mCertSpinner.setEnabled(true); + mCertSpinner.setVisibility(View.VISIBLE); + mCheckAll.setEnabled(true); + mCheckAll.setVisibility(View.VISIBLE); + } + } + else + { + mCertSpinner.setEnabled(false); + mCertSpinner.setVisibility(View.GONE); + mCheckAll.setEnabled(false); + mCheckAll.setVisibility(View.GONE); + } + } + /** * Save or update the profile depending on whether we actually have a * profile object or not (this was created in updateProfileData) @@ -149,6 +317,11 @@ public class VpnProfileDetailActivity extends Activity mUsername.setError(getString(R.string.alert_text_no_input_username)); valid = false; } + if (!mCheckAuto.isChecked() && mSelectedCert == null) + { + showCertificateAlert(); + valid = false; + } return valid; } @@ -166,6 +339,8 @@ public class VpnProfileDetailActivity extends Activity String password = mPassword.getText().toString().trim(); password = password.isEmpty() ? null : password; mProfile.setPassword(password); + String certAlias = mCheckAuto.isChecked() ? null : mSelectedCert.mAlias; + mProfile.setCertificateAlias(certAlias); } /** @@ -183,6 +358,7 @@ public class VpnProfileDetailActivity extends Activity mGateway.setText(mProfile.getGateway()); mUsername.setText(mProfile.getUsername()); mPassword.setText(mProfile.getPassword()); + mCertAlias = mProfile.getCertificateAlias(); getActionBar().setTitle(mProfile.getName()); } else @@ -192,5 +368,8 @@ public class VpnProfileDetailActivity extends Activity finish(); } } + mCheckAll.setChecked(false); + mCheckAuto.setChecked(mCertAlias == null); + updateCertSpinner(); } } From b1340aa12959b28f103a4c58d7db578de717fd9c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 18:44:06 +0200 Subject: [PATCH 064/119] Prompt the user for a password if none is configured in the VPN profile --- .../android/res/layout/login_dialog.xml | 51 ++++++++++++++++++ src/frontends/android/res/values/strings.xml | 4 ++ .../strongswan/android/ui/MainActivity.java | 53 ++++++++++++++++++- 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/res/layout/login_dialog.xml diff --git a/src/frontends/android/res/layout/login_dialog.xml b/src/frontends/android/res/layout/login_dialog.xml new file mode 100644 index 000000000..0262af0a3 --- /dev/null +++ b/src/frontends/android/res/layout/login_dialog.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index f2db4a676..6e6fa3aa6 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -50,4 +50,8 @@ No CA certificate selected Please select one or activate Select automatically + + Enter password to connect + Connect + diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 35f8c653f..7387dab8d 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -26,14 +26,23 @@ import org.strongswan.android.ui.VpnProfileListFragment.OnVpnProfileSelectedList import android.app.ActionBar; import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.net.VpnService; import android.os.AsyncTask; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import android.view.Window; +import android.widget.EditText; public class MainActivity extends Activity implements OnVpnProfileSelectedListener { @@ -102,6 +111,8 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen { Intent intent = new Intent(this, CharonVpnService.class); intent.putExtra(VpnProfileDataSource.KEY_ID, activeProfile.getId()); + /* submit the password as the profile might not store one */ + intent.putExtra(VpnProfileDataSource.KEY_PASSWORD, activeProfile.getPassword()); this.startService(intent); } break; @@ -114,7 +125,14 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen public void onVpnProfileSelected(VpnProfile profile) { activeProfile = profile; - prepareVpnService(); + if (activeProfile.getPassword() == null) + { + new LoginDialog().show(getFragmentManager(), "LoginDialog"); + } + else + { + prepareVpnService(); + } } /** @@ -142,4 +160,37 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen setProgressBarIndeterminateVisibility(false); } } + + private class LoginDialog extends DialogFragment + { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) + { + LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.login_dialog, null); + EditText username = (EditText)view.findViewById(R.id.username); + username.setText(activeProfile.getUsername()); + final EditText password = (EditText)view.findViewById(R.id.password); + + Builder adb = new AlertDialog.Builder(MainActivity.this); + adb.setView(view); + adb.setTitle(getString(R.string.login_title)); + adb.setPositiveButton(R.string.login_confirm, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) + { + activeProfile.setPassword(password.getText().toString().trim()); + prepareVpnService(); + } + }); + adb.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dismiss(); + } + }); + return adb.create(); + } + } } From 8d4eea53250f3b6864b3c53187ebfe15ea695bd3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 18:44:36 +0200 Subject: [PATCH 065/119] Allow VpnProfile objects to be cloned --- .../org/strongswan/android/data/VpnProfile.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java index 3e248d2eb..053f91555 100644 --- a/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java +++ b/src/frontends/android/src/org/strongswan/android/data/VpnProfile.java @@ -17,7 +17,7 @@ package org.strongswan.android.data; -public class VpnProfile +public class VpnProfile implements Cloneable { private String mName, mGateway, mUsername, mPassword, mCertificate; private long mId = -1; @@ -97,4 +97,17 @@ public class VpnProfile } return false; } + + @Override + public VpnProfile clone() + { + try + { + return (VpnProfile)super.clone(); + } + catch (CloneNotSupportedException e) + { + throw new AssertionError(); + } + } } From 6e04147743d770909acf197401b25c875d7c20e5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 7 Aug 2012 18:45:03 +0200 Subject: [PATCH 066/119] Clone the current VPN profile before updating the password Storing the password on the original object would be problematic in case the user mistypes the password (no prompt would be shown the second time). An alternative would be to just return the ID of the selected profile and then fetch it from the database. --- .../android/src/org/strongswan/android/ui/MainActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 7387dab8d..50b2bfbe5 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -179,6 +179,8 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen @Override public void onClick(DialogInterface dialog, int whichButton) { + /* let's work on a clone of the profile when updating the password */ + activeProfile = activeProfile.clone(); activeProfile.setPassword(password.getText().toString().trim()); prepareVpnService(); } From 24447cf49f007993763d1cc5c7f29b4408a495db Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 11:05:07 +0200 Subject: [PATCH 067/119] Add an Android specific kernel_net_t implementation This currently provides only no-ops and is just added because a kernel-net implementation is required and kernel-netlink can't be used at the moment. --- .../android/jni/libandroidbridge/Android.mk | 3 +- .../jni/libandroidbridge/charonservice.c | 8 +++ .../jni/libandroidbridge/charonservice.h | 3 + .../jni/libandroidbridge/kernel/android_net.c | 64 +++++++++++++++++++ .../jni/libandroidbridge/kernel/android_net.h | 49 ++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_net.c create mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_net.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index fe6cd6c1d..74cbeb730 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -4,7 +4,8 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ -charonservice.c charonservice.h +charonservice.c charonservice.h \ +kernel/android_net.c kernel/android_net.h # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 75d0da693..caba78988 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -21,6 +21,7 @@ #include "charonservice.h" #include "android_jni.h" +#include "kernel/android_net.h" #include #include @@ -94,6 +95,10 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) static void charonservice_init(JNIEnv *env, jobject service) { private_charonservice_t *this; + static plugin_feature_t features[] = { + PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-net"), + }; INIT(this, .public = { @@ -102,6 +107,9 @@ static void charonservice_init(JNIEnv *env, jobject service) ); charonservice = &this->public; + lib->plugins->add_static_features(lib->plugins, "androidbridge", features, + countof(features), TRUE); + lib->settings->set_int(lib->settings, "charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL); } diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 0788ced94..a3562490b 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -18,6 +18,9 @@ /** * @defgroup libandroidbridge libandroidbridge * + * @defgroup android_kernel kernel + * @ingroup libandroidbridge + * * @defgroup charonservice charonservice * @{ @ingroup libandroidbridge */ diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c new file mode 100644 index 000000000..e29f95510 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "android_net.h" + +typedef struct private_kernel_android_net_t private_kernel_android_net_t; + +struct private_kernel_android_net_t { + + /** + * Public kernel interface + */ + kernel_android_net_t public; +}; + +METHOD(kernel_net_t, add_ip, status_t, + private_kernel_android_net_t *this, host_t *virtual_ip, host_t *iface_ip) +{ + /* we get the IP from the IKE_SA once the CHILD_SA is established */ + return SUCCESS; +} + +METHOD(kernel_net_t, destroy, void, + private_kernel_android_net_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +kernel_android_net_t *kernel_android_net_create() +{ + private_kernel_android_net_t *this; + + INIT(this, + .public = { + .interface = { + .get_source_addr = (void*)return_null, + .get_nexthop = (void*)return_null, + .get_interface = (void*)return_null, + .create_address_enumerator = (void*)enumerator_create_empty, + .add_ip = _add_ip, + .del_ip = (void*)return_failed, + .add_route = (void*)return_failed, + .del_route = (void*)return_failed, + .destroy = _destroy, + }, + }, + ); + + return &this->public; +}; diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_net.h b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h new file mode 100644 index 000000000..470029fad --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_net.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup kernel_android_net kernel_android_net + * @{ @ingroup kernel_android + */ + +#ifndef KERNEL_ANDROID_NET_H_ +#define KERNEL_ANDROID_NET_H_ + +#include +#include + +typedef struct kernel_android_net_t kernel_android_net_t; + +/** + * Implementation of the kernel-net interface. This currently consists of only + * noops because a kernel_net_t implementation is required and we can't use + * kernel_netlink_net_t at the moment. + */ +struct kernel_android_net_t { + + /** + * Implements kernel_net_t interface + */ + kernel_net_t interface; +}; + +/** + * Create a android net interface instance. + * + * @return kernel_android_net_t instance + */ +kernel_android_net_t *kernel_android_net_create(); + +#endif /** KERNEL_ANDROID_NET_H_ @}*/ From 175088517fa064c869d2164e9af50bc4234e61c2 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 11:12:55 +0200 Subject: [PATCH 068/119] Add an Android specific kernel_ipsec_t implementation This is pretty much a proxy class that delegates everything (that is currently supported) to libipsec. --- .../android/jni/libandroidbridge/Android.mk | 1 + .../jni/libandroidbridge/charonservice.c | 3 + .../libandroidbridge/kernel/android_ipsec.c | 192 ++++++++++++++++++ .../libandroidbridge/kernel/android_ipsec.h | 48 +++++ 4 files changed, 244 insertions(+) create mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c create mode 100644 src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index 74cbeb730..95cc2b7db 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -5,6 +5,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ charonservice.c charonservice.h \ +kernel/android_ipsec.c kernel/android_ipsec.h \ kernel/android_net.c kernel/android_net.h # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index caba78988..a9a3fe4a6 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -21,6 +21,7 @@ #include "charonservice.h" #include "android_jni.h" +#include "kernel/android_ipsec.h" #include "kernel/android_net.h" #include @@ -98,6 +99,8 @@ static void charonservice_init(JNIEnv *env, jobject service) static plugin_feature_t features[] = { PLUGIN_CALLBACK(kernel_net_register, kernel_android_net_create), PLUGIN_PROVIDE(CUSTOM, "kernel-net"), + PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), + PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), }; INIT(this, diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c new file mode 100644 index 000000000..8254c0191 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "android_ipsec.h" + +#include +#include +#include +#include + +typedef struct private_kernel_android_ipsec_t private_kernel_android_ipsec_t; + +struct private_kernel_android_ipsec_t { + + /** + * Public kernel interface + */ + kernel_android_ipsec_t public; + + /** + * Listener for lifetime expire events + */ + ipsec_event_listener_t ipsec_listener; +}; + +/** + * Callback registrered with libipsec. + */ +void expire(u_int32_t reqid, u_int8_t protocol, u_int32_t spi, bool hard) +{ + hydra->kernel_interface->expire(hydra->kernel_interface, reqid, protocol, + spi, hard); +} + +METHOD(kernel_ipsec_t, get_spi, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int8_t protocol, u_int32_t reqid, u_int32_t *spi) +{ + return ipsec->sas->get_spi(ipsec->sas, src, dst, protocol, reqid, spi); +} + +METHOD(kernel_ipsec_t, get_cpi, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t reqid, u_int16_t *cpi) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, add_sa, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark, + u_int32_t tfc, lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key, + u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, u_int16_t ipcomp, + u_int16_t cpi, bool encap, bool esn, bool inbound, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts) +{ + return ipsec->sas->add_sa(ipsec->sas, src, dst, spi, protocol, reqid, mark, + tfc, lifetime, enc_alg, enc_key, int_alg, int_key, + mode, ipcomp, cpi, encap, esn, inbound, src_ts, + dst_ts); +} + +METHOD(kernel_ipsec_t, update_sa, status_t, + private_kernel_android_ipsec_t *this, u_int32_t spi, u_int8_t protocol, + u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst, + bool encap, bool new_encap, mark_t mark) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, query_sa, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, mark_t mark, u_int64_t *bytes) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, del_sa, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark) +{ + return ipsec->sas->del_sa(ipsec->sas, src, dst, spi, protocol, cpi, mark); +} + +METHOD(kernel_ipsec_t, flush_sas, status_t, + private_kernel_android_ipsec_t *this) +{ + return ipsec->sas->flush_sas(ipsec->sas); +} + +METHOD(kernel_ipsec_t, add_policy, status_t, + private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, + policy_priority_t priority) +{ + return ipsec->policies->add_policy(ipsec->policies, src, dst, src_ts, + dst_ts, direction, type, sa, mark, + priority); +} + +METHOD(kernel_ipsec_t, query_policy, status_t, + private_kernel_android_ipsec_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark, + u_int32_t *use_time) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, del_policy, status_t, + private_kernel_android_ipsec_t *this, traffic_selector_t *src_ts, + traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, + mark_t mark, policy_priority_t priority) +{ + return ipsec->policies->del_policy(ipsec->policies, src_ts, dst_ts, + direction, reqid, mark, priority); +} + +METHOD(kernel_ipsec_t, flush_policies, status_t, + private_kernel_android_ipsec_t *this) +{ + ipsec->policies->flush_policies(ipsec->policies); + return SUCCESS; +} + +METHOD(kernel_ipsec_t, bypass_socket, bool, + private_kernel_android_ipsec_t *this, int fd, int family) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, enable_udp_decap, bool, + private_kernel_android_ipsec_t *this, int fd, int family, u_int16_t port) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, destroy, void, + private_kernel_android_ipsec_t *this) +{ + ipsec->events->unregister_listener(ipsec->events, &this->ipsec_listener); + free(this); +} + +/* + * Described in header. + */ +kernel_android_ipsec_t *kernel_android_ipsec_create() +{ + private_kernel_android_ipsec_t *this; + + INIT(this, + .public = { + .interface = { + .get_spi = _get_spi, + .get_cpi = _get_cpi, + .add_sa = _add_sa, + .update_sa = _update_sa, + .query_sa = _query_sa, + .del_sa = _del_sa, + .flush_sas = _flush_sas, + .add_policy = _add_policy, + .query_policy = _query_policy, + .del_policy = _del_policy, + .flush_policies = _flush_policies, + .bypass_socket = _bypass_socket, + .enable_udp_decap = _enable_udp_decap, + .destroy = _destroy, + }, + }, + .ipsec_listener = { + .expire = expire, + }, + ); + + ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h new file mode 100644 index 000000000..3a2e8343f --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup kernel_android_ipsec kernel_android_ipsec + * @{ @ingroup kernel_android + */ + +#ifndef KERNEL_ANDROID_IPSEC_H_ +#define KERNEL_ANDROID_IPSEC_H_ + +#include +#include + +typedef struct kernel_android_ipsec_t kernel_android_ipsec_t; + +/** + * Implementation of the ipsec interface using libipsec on Android + */ +struct kernel_android_ipsec_t { + + /** + * Implements kernel_ipsec_t interface + */ + kernel_ipsec_t interface; +}; + +/** + * Create a android ipsec interface instance. + * + * @return kernel_android_ipsec_t instance + */ +kernel_android_ipsec_t *kernel_android_ipsec_create(); + +#endif /** KERNEL_ANDROID_IPSEC_H_ @}*/ From d1220566ef2e69027bee602a0734b19c4bfab86c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 11:32:03 +0200 Subject: [PATCH 069/119] Service added that keeps track of VPN state and notifies listeners about changes It is ensured that listeners are notified only from the main thread. --- src/frontends/android/AndroidManifest.xml | 4 + .../android/logic/VpnStateService.java | 245 ++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 7dcbf010c..5c8686e79 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -25,6 +25,10 @@ android:name=".ui.VpnProfileDetailActivity" > + + . + * + * 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. + */ + +package org.strongswan.android.logic; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import org.strongswan.android.data.VpnProfile; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; + +public class VpnStateService extends Service +{ + private final List mListeners = new ArrayList(); + private final IBinder mBinder = new LocalBinder(); + private Handler mHandler; + private VpnProfile mProfile; + private State mState = State.DISABLED; + private ErrorState mError = ErrorState.NO_ERROR; + + public enum State + { + DISABLED, + CONNECTING, + CONNECTED, + DISCONNECTING, + } + + public enum ErrorState + { + NO_ERROR, + AUTH_FAILED, + PEER_AUTH_FAILED, + LOOKUP_FAILED, + UNREACHABLE, + GENERIC_ERROR, + } + + /** + * Listener interface for bound clients that are interested in changes to + * this Service. + */ + public interface VpnStateListener + { + public void stateChanged(); + } + + /** + * Simple Binder that allows to directly access this Service class itself + * after binding to it. + */ + public class LocalBinder extends Binder + { + public VpnStateService getService() + { + return VpnStateService.this; + } + } + + @Override + public void onCreate() + { + /* this handler allows us to notify listeners from the UI thread and + * not from the threads that actually report any state changes */ + mHandler = new Handler(); + } + + @Override + public IBinder onBind(Intent intent) + { + return mBinder; + } + + @Override + public void onDestroy() + { + } + + /** + * Register a listener with this Service. We assume this is called from + * the main thread so no synchronization is happening. + * + * @param listener listener to register + */ + public void registerListener(VpnStateListener listener) + { + mListeners.add(listener); + } + + /** + * Unregister a listener from this Service. + * + * @param listener listener to unregister + */ + public void unregisterListener(VpnStateListener listener) + { + mListeners.remove(listener); + } + + /** + * Get the current VPN profile. + * + * @return profile + */ + public VpnProfile getProfile() + { /* only updated from the main thread so no synchronization needed */ + return mProfile; + } + + /** + * Get the current state. + * + * @return state + */ + public State getState() + { /* only updated from the main thread so no synchronization needed */ + return mState; + } + + /** + * Get the current error, if any. + * + * @return error + */ + public ErrorState getErrorState() + { /* only updated from the main thread so no synchronization needed */ + return mError; + } + + /** + * Update state and notify all listeners about the change. By using a Handler + * this is done from the main UI thread and not the initial reporter thread. + * Also, in doing the actual state change from the main thread, listeners + * see all changes and none are skipped. + * + * @param change the state update to perform before notifying listeners, returns true if state changed + */ + private void notifyListeners(final Callable change) + { + mHandler.post(new Runnable() { + @Override + public void run() + { + try + { + if (change.call()) + { /* otherwise there is no need to notify the listeners */ + for (VpnStateListener listener : mListeners) + { + listener.stateChanged(); + } + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + } + + /** + * Set the VPN profile currently active. Listeners are not notified. + * + * May be called from threads other than the main thread. + * + * @param profile current profile + */ + public void setProfile(final VpnProfile profile) + { + /* even though we don't notify the listeners the update is done from the + * same handler so updates are predictable for listeners */ + mHandler.post(new Runnable() { + @Override + public void run() + { + VpnStateService.this.mProfile = profile; + } + }); + } + + /** + * Update the state and notify all listeners, if changed. + * + * May be called from threads other than the main thread. + * + * @param state new state + */ + public void setState(final State state) + { + notifyListeners(new Callable() { + @Override + public Boolean call() throws Exception + { + if (VpnStateService.this.mState != state) + { + VpnStateService.this.mState = state; + return true; + } + return false; + } + }); + } + + /** + * Set the current error state and notify all listeners, if changed. + * + * May be called from threads other than the main thread. + * + * @param error error state + */ + public void setError(final ErrorState error) + { + notifyListeners(new Callable() { + @Override + public Boolean call() throws Exception + { + if (VpnStateService.this.mError != error) + { + VpnStateService.this.mError = error; + return true; + } + return false; + } + }); + } +} From a4f9028e08d6e7398a35b81608c4492edf7f467a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 11:54:36 +0200 Subject: [PATCH 070/119] CharonVpnService reacts on Intents and properly inits/deinits charon Charon is initialized with every new connection attempt and deinitialized when the service is terminated or it receives an empty Intent (or before starting a new connection). A separate thread is used to handle the connection attempts, this thread acts as main thread for charon. --- .../android/logic/CharonVpnService.java | 151 ++++++++++++++++-- 1 file changed, 140 insertions(+), 11 deletions(-) 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 b32f9ae87..2f11cf1e8 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -1,34 +1,163 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + package org.strongswan.android.logic; +import org.strongswan.android.data.VpnProfile; +import org.strongswan.android.data.VpnProfileDataSource; + import android.content.Intent; import android.net.VpnService; +import android.os.Bundle; +import android.util.Log; -public class CharonVpnService extends VpnService +public class CharonVpnService extends VpnService implements Runnable { + private static final String TAG = CharonVpnService.class.getSimpleName(); + private VpnProfileDataSource mDataSource; + private Thread mConnectionHandler; + private VpnProfile mCurrentProfile; + private VpnProfile mNextProfile; + private volatile boolean mProfileUpdated; + private volatile boolean mTerminate; @Override public int onStartCommand(Intent intent, int flags, int startId) { - // called whenever the service is started with startService - // create our own thread because we are running in the calling processes - // main thread - return super.onStartCommand(intent, flags, startId); + if (intent != null) + { + Bundle bundle = intent.getExtras(); + VpnProfile profile = null; + if (bundle != null) + { + profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID)); + if (profile != null) + { + String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD); + profile.setPassword(password); + } + } + setNextProfile(profile); + } + return START_NOT_STICKY; } @Override public void onCreate() { - // onCreate is only called once - initializeCharon(); - super.onCreate(); + mDataSource = new VpnProfileDataSource(this); + mDataSource.open(); + /* use a separate thread as main thread for charon */ + mConnectionHandler = new Thread(this); + mConnectionHandler.start(); + } + + @Override + public void onRevoke() + { /* the system revoked the rights grated with the initial prepare() call. + * called when the user clicks disconnect in the system's VPN dialog */ + setNextProfile(null); } @Override public void onDestroy() { - // called once the service is to be destroyed - deinitializeCharon(); - super.onDestroy(); + mTerminate = true; + setNextProfile(null); + try + { + mConnectionHandler.join(); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + mDataSource.close(); + } + + /** + * Set the profile that is to be initiated next. Notify the handler thread. + * + * @param profile the profile to initiate + */ + private void setNextProfile(VpnProfile profile) + { + synchronized (this) + { + this.mNextProfile = profile; + mProfileUpdated = true; + notifyAll(); + } + } + + @Override + public void run() + { + while (true) + { + synchronized (this) + { + try + { + while (!mProfileUpdated) + { + wait(); + } + + mProfileUpdated = false; + stopCurrentConnection(); + if (mNextProfile == null) + { + if (mTerminate) + { + break; + } + } + else + { + mCurrentProfile = mNextProfile; + mNextProfile = null; + + initializeCharon(); + Log.i(TAG, "charon started"); + } + } + catch (InterruptedException ex) + { + stopCurrentConnection(); + } + } + } + } + + /** + * Stop any existing connection by deinitializing charon. + */ + private void stopCurrentConnection() + { + synchronized (this) + { + if (mCurrentProfile != null) + { + deinitializeCharon(); + Log.i(TAG, "charon stopped"); + mCurrentProfile = null; + } + } } /** From 03de55ad987ea40dbb2446091cea3a1b87b84ff7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:04:38 +0200 Subject: [PATCH 071/119] CharonVpnService binds to VpnStateService and does basic state updates --- .../android/logic/CharonVpnService.java | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) 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 2f11cf1e8..083b98c0c 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -19,10 +19,16 @@ package org.strongswan.android.logic; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; +import org.strongswan.android.logic.VpnStateService.ErrorState; +import org.strongswan.android.logic.VpnStateService.State; +import android.app.Service; +import android.content.ComponentName; import android.content.Intent; +import android.content.ServiceConnection; import android.net.VpnService; import android.os.Bundle; +import android.os.IBinder; import android.util.Log; public class CharonVpnService extends VpnService implements Runnable @@ -34,6 +40,29 @@ public class CharonVpnService extends VpnService implements Runnable private VpnProfile mNextProfile; private volatile boolean mProfileUpdated; private volatile boolean mTerminate; + private VpnStateService mService; + private final Object mServiceLock = new Object(); + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { /* since the service is local this is theoretically only called when the process is terminated */ + synchronized (mServiceLock) + { + mService = null; + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + synchronized (mServiceLock) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + } + /* we are now ready to start the handler thread */ + mConnectionHandler.start(); + } + }; @Override public int onStartCommand(Intent intent, int flags, int startId) @@ -63,7 +92,9 @@ public class CharonVpnService extends VpnService implements Runnable mDataSource.open(); /* use a separate thread as main thread for charon */ mConnectionHandler = new Thread(this); - mConnectionHandler.start(); + /* the thread is started when the service is bound */ + bindService(new Intent(this, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); } @Override @@ -86,6 +117,10 @@ public class CharonVpnService extends VpnService implements Runnable { e.printStackTrace(); } + if (mService != null) + { + unbindService(mServiceConnection); + } mDataSource.close(); } @@ -122,6 +157,8 @@ public class CharonVpnService extends VpnService implements Runnable stopCurrentConnection(); if (mNextProfile == null) { + setProfile(null); + setState(State.DISABLED); if (mTerminate) { break; @@ -132,6 +169,10 @@ public class CharonVpnService extends VpnService implements Runnable mCurrentProfile = mNextProfile; mNextProfile = null; + setProfile(mCurrentProfile); + setError(ErrorState.NO_ERROR); + setState(State.CONNECTING); + initializeCharon(); Log.i(TAG, "charon started"); } @@ -139,6 +180,7 @@ public class CharonVpnService extends VpnService implements Runnable catch (InterruptedException ex) { stopCurrentConnection(); + setState(State.DISABLED); } } } @@ -153,6 +195,7 @@ public class CharonVpnService extends VpnService implements Runnable { if (mCurrentProfile != null) { + setState(State.DISCONNECTING); deinitializeCharon(); Log.i(TAG, "charon stopped"); mCurrentProfile = null; @@ -160,6 +203,56 @@ public class CharonVpnService extends VpnService implements Runnable } } + /** + * Update the VPN profile on the state service. Called by the handler thread. + * + * @param profile currently active VPN profile + */ + private void setProfile(VpnProfile profile) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setProfile(profile); + } + } + } + + /** + * Update the current VPN state on the state service. Called by the handler + * thread and any of charon's threads. + * + * @param state current state + */ + private void setState(State state) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setState(state); + } + } + } + + /** + * Set an error on the state service. Called by the handler thread and any + * of charon's threads. + * + * @param error error state + */ + private void setError(ErrorState error) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setError(error); + } + } + } + /** * Initialization of charon, provided by libandroidbridge.so */ From d4f76751992579d9fcbb636fff0ed4429bbfc75e Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:20:13 +0200 Subject: [PATCH 072/119] Implement kernel_ipsec_t.bypass_socket() via JNI and VpnService.protect() --- .../jni/libandroidbridge/charonservice.c | 29 +++++++++++++++++++ .../jni/libandroidbridge/charonservice.h | 12 ++++++++ .../libandroidbridge/kernel/android_ipsec.c | 3 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index a9a3fe4a6..974875e6e 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -90,6 +90,34 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) } } +METHOD(charonservice_t, bypass_socket, bool, + private_charonservice_t *this, int fd, int family) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "protect", "(I)Z"); + if (!method_id) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->vpn_service, method_id, fd)) + { + DBG1(DBG_CFG, "VpnService.protect() failed"); + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + /** * Initialize the charonservice object */ @@ -105,6 +133,7 @@ static void charonservice_init(JNIEnv *env, jobject service) INIT(this, .public = { + .bypass_socket = _bypass_socket, }, .vpn_service = (*env)->NewGlobalRef(env, service), ); diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index a3562490b..8bacd0a1d 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -28,6 +28,8 @@ #ifndef CHARONSERVICE_H_ #define CHARONSERVICE_H_ +#include + typedef struct charonservice_t charonservice_t; /** @@ -37,6 +39,16 @@ typedef struct charonservice_t charonservice_t; */ struct charonservice_t { + /** + * Install a bypass policy for the given socket using the protect() Method + * of the Android VpnService interface + * + * @param fd socket file descriptor + * @param family socket protocol family + * @return TRUE if operation successful + */ + bool (*bypass_socket)(charonservice_t *this, int fd, int family); + }; /** diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c index 8254c0191..08cc61610 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c @@ -15,6 +15,7 @@ */ #include "android_ipsec.h" +#include "../charonservice.h" #include #include @@ -139,7 +140,7 @@ METHOD(kernel_ipsec_t, flush_policies, status_t, METHOD(kernel_ipsec_t, bypass_socket, bool, private_kernel_android_ipsec_t *this, int fd, int family) { - return NOT_SUPPORTED; + return charonservice->bypass_socket(charonservice, fd, family); } METHOD(kernel_ipsec_t, enable_udp_decap, bool, From 1b8877727c1c228fb9411daf1cec9a63d83c4d2c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:25:17 +0200 Subject: [PATCH 073/119] Add a function to disconnect any current VPN connection --- .../android/logic/VpnStateService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java b/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java index 2e2bce541..1c14cb601 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/VpnStateService.java @@ -22,6 +22,7 @@ import java.util.concurrent.Callable; import org.strongswan.android.data.VpnProfile; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.Handler; @@ -145,6 +146,24 @@ public class VpnStateService extends Service return mError; } + /** + * Disconnect any existing connection and shutdown the daemon, the + * VpnService is not stopped but it is reset so new connections can be + * started. + */ + public void disconnect() + { + /* as soon as the TUN device is created by calling establish() on the + * VpnService.Builder object the system binds to the service and keeps + * bound until the file descriptor of the TUN device is closed. thus + * calling stopService() here would not stop (destroy) the service yet, + * instead we call startService() with an empty Intent which shuts down + * the daemon (and closes the TUN device, if any) */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, CharonVpnService.class); + context.startService(intent); + } + /** * Update state and notify all listeners about the change. By using a Handler * this is done from the main UI thread and not the initial reporter thread. From 8c2af60ceb5252312cc1f6410d87cac48baf0338 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:31:58 +0200 Subject: [PATCH 074/119] Function added that allows to update VPN state via JNI --- .../jni/libandroidbridge/charonservice.c | 25 +++++++ .../jni/libandroidbridge/charonservice.h | 22 ++++++ .../android/logic/CharonVpnService.java | 75 +++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 974875e6e..874258b3b 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -90,6 +90,30 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) } } +METHOD(charonservice_t, update_status, bool, + private_charonservice_t *this, android_vpn_state_t code) +{ + JNIEnv *env; + jmethodID method_id; + bool success = FALSE; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_class, + "updateStatus", "(I)V"); + if (!method_id) + { + goto failed; + } + (*env)->CallVoidMethod(env, this->vpn_service, method_id, (jint)code); + success = !androidjni_exception_occurred(env); + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return success; +} + METHOD(charonservice_t, bypass_socket, bool, private_charonservice_t *this, int fd, int family) { @@ -133,6 +157,7 @@ static void charonservice_init(JNIEnv *env, jobject service) INIT(this, .public = { + .update_status = _update_status, .bypass_socket = _bypass_socket, }, .vpn_service = (*env)->NewGlobalRef(env, service), diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index 8bacd0a1d..c53716588 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -30,8 +30,22 @@ #include +typedef enum android_vpn_state_t android_vpn_state_t; typedef struct charonservice_t charonservice_t; +/** + * VPN status codes. As defined in CharonVpnService.java + */ +enum android_vpn_state_t { + CHARONSERVICE_CHILD_STATE_UP = 1, + CHARONSERVICE_CHILD_STATE_DOWN, + CHARONSERVICE_AUTH_ERROR, + CHARONSERVICE_PEER_AUTH_ERROR, + CHARONSERVICE_LOOKUP_ERROR, + CHARONSERVICE_UNREACHABLE_ERROR, + CHARONSERVICE_GENERIC_ERROR, +}; + /** * Public interface of charonservice. * @@ -39,6 +53,14 @@ typedef struct charonservice_t charonservice_t; */ struct charonservice_t { + /** + * Update the status in the Java domain (UI) + * + * @param code status code + * @return TRUE on success + */ + bool (*update_status)(charonservice_t *this, android_vpn_state_t code); + /** * Install a bypass policy for the given socket using the protect() Method * of the Android VpnService interface 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 083b98c0c..c3bb1adc6 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -64,6 +64,17 @@ public class CharonVpnService extends VpnService implements Runnable } }; + /** + * as defined in charonservice.h + */ + static final int STATE_CHILD_SA_UP = 1; + static final int STATE_CHILD_SA_DOWN = 2; + static final int STATE_AUTH_ERROR = 3; + static final int STATE_PEER_AUTH_ERROR = 4; + static final int STATE_LOOKUP_ERROR = 5; + static final int STATE_UNREACHABLE_ERROR = 6; + static final int STATE_GENERIC_ERROR = 7; + @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -253,6 +264,70 @@ public class CharonVpnService extends VpnService implements Runnable } } + /** + * Set an error on the state service and disconnect the current connection. + * This is not done by calling stopCurrentConnection() above, but instead + * is done asynchronously via state service. + * + * @param error error state + */ + private void setErrorDisconnect(ErrorState error) + { + synchronized (mServiceLock) + { + if (mService != null) + { + mService.setError(error); + mService.disconnect(); + } + } + } + + /** + * Updates the state of the current connection. + * Called via JNI by different threads (but not concurrently). + * + * @param status new state + */ + public void updateStatus(int status) + { + switch (status) + { + case STATE_CHILD_SA_DOWN: + synchronized (mServiceLock) + { + /* since this state is also reached when the SA is closed remotely, + * we call disconnect() to make sure charon is properly deinitialized */ + if (mService != null) + { + mService.disconnect(); + } + } + break; + case STATE_CHILD_SA_UP: + setState(State.CONNECTED); + break; + case STATE_AUTH_ERROR: + setErrorDisconnect(ErrorState.AUTH_FAILED); + break; + case STATE_PEER_AUTH_ERROR: + setErrorDisconnect(ErrorState.PEER_AUTH_FAILED); + break; + case STATE_LOOKUP_ERROR: + setErrorDisconnect(ErrorState.LOOKUP_FAILED); + break; + case STATE_UNREACHABLE_ERROR: + setErrorDisconnect(ErrorState.UNREACHABLE); + break; + case STATE_GENERIC_ERROR: + setErrorDisconnect(ErrorState.GENERIC_ERROR); + break; + default: + Log.e(TAG, "Unknown status code received"); + break; + } + } + /** * Initialization of charon, provided by libandroidbridge.so */ From 2bec193a1ba26f04f16169811c92df97ad3389dd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:35:49 +0200 Subject: [PATCH 075/119] CharonVpnService provides a function to get trusted certificates via JNI --- .../jni/libandroidbridge/charonservice.c | 48 +++++++++++++++ .../jni/libandroidbridge/charonservice.h | 9 +++ .../android/logic/CharonVpnService.java | 61 +++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 874258b3b..ac6df0d37 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -142,6 +142,53 @@ failed: return FALSE; } +METHOD(charonservice_t, get_trusted_certificates, linked_list_t*, + private_charonservice_t *this) +{ + JNIEnv *env; + jmethodID method_id; + jobjectArray jcerts; + linked_list_t *list; + jsize i; + + androidjni_attach_thread(&env); + + method_id = (*env)->GetMethodID(env, + android_charonvpnservice_class, + "getTrustedCertificates", "(Ljava/lang/String;)[[B"); + if (!method_id) + { + goto failed; + } + jcerts = (*env)->CallObjectMethod(env, this->vpn_service, method_id, NULL); + if (!jcerts) + { + goto failed; + } + list = linked_list_create(); + for (i = 0; i < (*env)->GetArrayLength(env, jcerts); ++i) + { + chunk_t *ca_cert; + jbyteArray jcert; + + ca_cert = malloc_thing(chunk_t); + list->insert_last(list, ca_cert); + + jcert = (*env)->GetObjectArrayElement(env, jcerts, i); + *ca_cert = chunk_alloc((*env)->GetArrayLength(env, jcert)); + (*env)->GetByteArrayRegion(env, jcert, 0, ca_cert->len, ca_cert->ptr); + (*env)->DeleteLocalRef(env, jcert); + } + (*env)->DeleteLocalRef(env, jcerts); + androidjni_detach_thread(); + return list; + +failed: + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return NULL; +} + /** * Initialize the charonservice object */ @@ -159,6 +206,7 @@ static void charonservice_init(JNIEnv *env, jobject service) .public = { .update_status = _update_status, .bypass_socket = _bypass_socket, + .get_trusted_certificates = _get_trusted_certificates, }, .vpn_service = (*env)->NewGlobalRef(env, service), ); diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index c53716588..d0c0b71a5 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -29,6 +29,7 @@ #define CHARONSERVICE_H_ #include +#include typedef enum android_vpn_state_t android_vpn_state_t; typedef struct charonservice_t charonservice_t; @@ -71,6 +72,14 @@ struct charonservice_t { */ bool (*bypass_socket)(charonservice_t *this, int fd, int family); + /** + * Get a list of trusted certificates via JNI + * + * @return list of DER encoded certificates (as chunk_t*), + * NULL on failure + */ + linked_list_t *(*get_trusted_certificates)(charonservice_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 c3bb1adc6..d66a5517d 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -17,6 +17,10 @@ package org.strongswan.android.logic; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; + import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.logic.VpnStateService.ErrorState; @@ -328,6 +332,63 @@ public class CharonVpnService extends VpnService implements Runnable } } + /** + * Function called via JNI to generate a list of DER encoded CA certificates + * as byte array. + * + * @param hash optional alias (only hash part), if given matching certificates are returned + * @return a list of DER encoded CA certificates + */ + private synchronized byte[][] getTrustedCertificates(String hash) + { + ArrayList certs = new ArrayList(); + TrustedCertificateManager certman = TrustedCertificateManager.getInstance(); + try + { + if (hash != null) + { + String alias = "user:" + hash + ".0"; + X509Certificate cert = certman.getCACertificateFromAlias(alias); + if (cert == null) + { + alias = "system:" + hash + ".0"; + cert = certman.getCACertificateFromAlias(alias); + } + if (cert == null) + { + return null; + } + certs.add(cert.getEncoded()); + } + else + { + String alias = this.mCurrentProfile.getCertificateAlias(); + if (alias != null) + { + X509Certificate cert = certman.getCACertificateFromAlias(alias); + if (cert == null) + { + return null; + } + certs.add(cert.getEncoded()); + } + else + { + for (X509Certificate cert : certman.getAllCACertificates().values()) + { + certs.add(cert.getEncoded()); + } + } + } + } + catch (CertificateEncodingException e) + { + e.printStackTrace(); + return null; + } + return certs.toArray(new byte[certs.size()][]); + } + /** * Initialization of charon, provided by libandroidbridge.so */ From 8430e54d83e71b8cc806e12274905b6d0ea10f0c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:52:05 +0200 Subject: [PATCH 076/119] Added an Android specific credential set that provides CA certificates via JNI --- .../android/jni/libandroidbridge/Android.mk | 1 + .../libandroidbridge/backend/android_creds.c | 153 ++++++++++++++++++ .../libandroidbridge/backend/android_creds.h | 57 +++++++ .../jni/libandroidbridge/charonservice.c | 29 ++++ .../jni/libandroidbridge/charonservice.h | 3 + 5 files changed, 243 insertions(+) create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_creds.c create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_creds.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index 95cc2b7db..a56aa3b16 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -4,6 +4,7 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ +backend/android_creds.c backend/android_creds.h \ charonservice.c charonservice.h \ kernel/android_ipsec.c kernel/android_ipsec.h \ kernel/android_net.c kernel/android_net.h diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c new file mode 100644 index 000000000..ee9549d9c --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "android_creds.h" +#include "../charonservice.h" + +#include +#include +#include +#include + +typedef struct private_android_creds_t private_android_creds_t; + +/** + * Private data of an android_creds_t object + */ +struct private_android_creds_t { + + /** + * Public interface + */ + android_creds_t public; + + /** + * Credential set storing trusted certificates + */ + mem_cred_t *creds; + + /** + * read/write lock to make sure certificates are only loaded once + */ + rwlock_t *lock; + + /** + * TRUE if certificates have been loaded via JNI + */ + bool loaded; +}; + +/** + * Load trusted certificates via charonservice (JNI). + */ +static void load_trusted_certificates(private_android_creds_t *this) +{ + linked_list_t *certs; + certificate_t *cert; + chunk_t *current; + + certs = charonservice->get_trusted_certificates(charonservice); + if (certs) + { + while (certs->remove_first(certs, (void**)¤t) == SUCCESS) + { + cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, + BUILD_BLOB_ASN1_DER, *current, BUILD_END); + if (cert) + { + DBG2(DBG_CFG, "loaded CA certificate '%Y'", + cert->get_subject(cert)); + this->creds->add_cert(this->creds, TRUE, cert); + } + chunk_free(current); + free(current); + } + certs->destroy(certs); + } +} + +METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, + private_android_creds_t *this, certificate_type_t cert, key_type_t key, + identification_t *id, bool trusted) +{ + enumerator_t *enumerator; + + if (!trusted || (cert != CERT_ANY && cert != CERT_X509)) + { + return NULL; + } + this->lock->read_lock(this->lock); + if (!this->loaded) + { + this->lock->unlock(this->lock); + this->lock->write_lock(this->lock); + /* check again after acquiring the write lock */ + if (!this->loaded) + { + load_trusted_certificates(this); + this->loaded = TRUE; + } + this->lock->unlock(this->lock); + this->lock->read_lock(this->lock); + } + enumerator = this->creds->set.create_cert_enumerator(&this->creds->set, + cert, key, id, trusted); + return enumerator_create_cleaner(enumerator, (void*)this->lock->unlock, + this->lock); +} + +METHOD(android_creds_t, clear, void, + private_android_creds_t *this) +{ + this->lock->write_lock(this->lock); + this->creds->clear(this->creds); + this->loaded = FALSE; + this->lock->unlock(this->lock); +} + +METHOD(android_creds_t, destroy, void, + private_android_creds_t *this) +{ + clear(this); + this->creds->destroy(this->creds); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +android_creds_t *android_creds_create() +{ + private_android_creds_t *this; + + INIT(this, + .public = { + .set = { + .create_cert_enumerator = _create_cert_enumerator, + .create_shared_enumerator = (void*)return_null, + .create_private_enumerator = (void*)return_null, + .create_cdp_enumerator = (void*)return_null, + .cache_cert = (void*)nop, + }, + .clear = _clear, + .destroy = _destroy, + }, + .creds = mem_cred_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h new file mode 100644 index 000000000..4b19b1bf5 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup android_creds android_creds + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_CREDS_H_ +#define ANDROID_CREDS_H_ + +#include +#include + +typedef struct android_creds_t android_creds_t; + +/** + * Android credential set that provides CA certificates via JNI. + */ +struct android_creds_t { + + /** + * Implements credential_set_t + */ + credential_set_t set; + + /** + * Clear the cached CA certificates. + */ + void (*clear)(android_creds_t *this); + + /** + * Destroy a android_creds instance. + */ + void (*destroy)(android_creds_t *this); + +}; + +/** + * Create an android_creds instance. + */ +android_creds_t *android_creds_create(); + +#endif /** ANDROID_CREDS_H_ @}*/ + diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index ac6df0d37..72feb9e96 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -21,6 +21,7 @@ #include "charonservice.h" #include "android_jni.h" +#include "backend/android_creds.h" #include "kernel/android_ipsec.h" #include "kernel/android_net.h" @@ -44,6 +45,11 @@ struct private_charonservice_t { */ charonservice_t public; + /** + * android_creds instance + */ + android_creds_t *creds; + /** * CharonVpnService reference */ @@ -189,6 +195,24 @@ failed: return NULL; } +/** + * Initialize/deinitialize Android backend + */ +static bool charonservice_register(void *plugin, plugin_feature_t *feature, + bool reg, void *data) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + if (reg) + { + lib->credmgr->add_set(lib->credmgr, &this->creds->set); + } + else + { + lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + } + return TRUE; +} + /** * Initialize the charonservice object */ @@ -200,6 +224,9 @@ static void charonservice_init(JNIEnv *env, jobject service) PLUGIN_PROVIDE(CUSTOM, "kernel-net"), PLUGIN_CALLBACK(kernel_ipsec_register, kernel_android_ipsec_create), PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"), + PLUGIN_CALLBACK((plugin_feature_callback_t)charonservice_register, NULL), + PLUGIN_PROVIDE(CUSTOM, "Android backend"), + PLUGIN_DEPENDS(CUSTOM, "libcharon"), }; INIT(this, @@ -208,6 +235,7 @@ static void charonservice_init(JNIEnv *env, jobject service) .bypass_socket = _bypass_socket, .get_trusted_certificates = _get_trusted_certificates, }, + .creds = android_creds_create(), .vpn_service = (*env)->NewGlobalRef(env, service), ); charonservice = &this->public; @@ -226,6 +254,7 @@ static void charonservice_deinit(JNIEnv *env) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + this->creds->destroy(this->creds); (*env)->DeleteGlobalRef(env, this->vpn_service); free(this); charonservice = NULL; diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index d0c0b71a5..e538a22c9 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -18,6 +18,9 @@ /** * @defgroup libandroidbridge libandroidbridge * + * @defgroup android_backend backend + * @ingroup libandroidbridge + * * @defgroup android_kernel kernel * @ingroup libandroidbridge * From 3aa5c609c39f7a6418f9bbf67d46293a77e023fe Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 12:59:39 +0200 Subject: [PATCH 077/119] Android specific credential set also provides user credentials --- .../libandroidbridge/backend/android_creds.c | 27 +++++++++++++++++-- .../libandroidbridge/backend/android_creds.h | 14 ++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c index ee9549d9c..27023d721 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.c @@ -34,7 +34,7 @@ struct private_android_creds_t { android_creds_t public; /** - * Credential set storing trusted certificates + * Credential set storing trusted certificates and user credentials */ mem_cred_t *creds; @@ -108,6 +108,28 @@ METHOD(credential_set_t, create_cert_enumerator, enumerator_t*, this->lock); } +METHOD(android_creds_t, add_username_password, void, + private_android_creds_t *this, char *username, char *password) +{ + shared_key_t *shared_key; + identification_t *id; + chunk_t secret; + + secret = chunk_create(password, strlen(password)); + shared_key = shared_key_create(SHARED_EAP, chunk_clone(secret)); + id = identification_create_from_string(username); + + this->creds->add_shared(this->creds, shared_key, id, NULL); +} + +METHOD(credential_set_t, create_shared_enumerator, enumerator_t*, + private_android_creds_t *this, shared_key_type_t type, + identification_t *me, identification_t *other) +{ + return this->creds->set.create_shared_enumerator(&this->creds->set, + type, me, other); +} + METHOD(android_creds_t, clear, void, private_android_creds_t *this) { @@ -137,11 +159,12 @@ android_creds_t *android_creds_create() .public = { .set = { .create_cert_enumerator = _create_cert_enumerator, - .create_shared_enumerator = (void*)return_null, + .create_shared_enumerator = _create_shared_enumerator, .create_private_enumerator = (void*)return_null, .create_cdp_enumerator = (void*)return_null, .cache_cert = (void*)nop, }, + .add_username_password = _add_username_password, .clear = _clear, .destroy = _destroy, }, diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h index 4b19b1bf5..33de838c1 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_creds.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_creds.h @@ -27,7 +27,8 @@ typedef struct android_creds_t android_creds_t; /** - * Android credential set that provides CA certificates via JNI. + * Android credential set that provides CA certificates via JNI and supplied + * user credentials. */ struct android_creds_t { @@ -37,7 +38,16 @@ struct android_creds_t { credential_set_t set; /** - * Clear the cached CA certificates. + * Add user name and password for EAP authentication + * + * @param username user name + * @param password password + */ + void (*add_username_password)(android_creds_t *this, char *username, + char *password); + + /** + * Clear the cached certificates and stored credentials. */ void (*clear)(android_creds_t *this); From 66211196a70301dca7d271aab3a9c55b66a91e14 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 13:15:53 +0200 Subject: [PATCH 078/119] android_service_t handles initiation of an SA and tracks its progress Status updates are delivered via charonservice (JNI). --- .../android/jni/libandroidbridge/Android.mk | 1 + .../backend/android_service.c | 269 ++++++++++++++++++ .../backend/android_service.h | 61 ++++ 3 files changed, 331 insertions(+) create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_service.c create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_service.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index a56aa3b16..9e1e94a8f 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -5,6 +5,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ backend/android_creds.c backend/android_creds.h \ +backend/android_service.c backend/android_service.h \ charonservice.c charonservice.h \ kernel/android_ipsec.c kernel/android_ipsec.h \ kernel/android_net.c kernel/android_net.h diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c new file mode 100644 index 000000000..5c18924be --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2010-2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "android_service.h" +#include "../charonservice.h" + +#include +#include +#include + +typedef struct private_android_service_t private_android_service_t; + +/** + * private data of Android service + */ +struct private_android_service_t { + + /** + * public interface + */ + android_service_t public; + + /** + * current IKE_SA + */ + ike_sa_t *ike_sa; + + /** + * local ipv4 address + */ + char *local_address; + + /** + * gateway + */ + char *gateway; + + /** + * username + */ + char *username; + +}; + +METHOD(listener_t, child_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, + bool up) +{ + if (this->ike_sa == ike_sa) + { + if (up) + { + /* disable the hooks registered to catch initiation failures */ + this->public.listener.ike_updown = NULL; + this->public.listener.ike_state_change = NULL; + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_UP); + } + else + { + charonservice->update_status(charonservice, + CHARONSERVICE_CHILD_STATE_DOWN); + return FALSE; + } + } + return TRUE; +} + +METHOD(listener_t, ike_updown, bool, + private_android_service_t *this, ike_sa_t *ike_sa, bool up) +{ + /* this callback is only registered during initiation, so if the IKE_SA + * goes down we assume an authentication error */ + if (this->ike_sa == ike_sa && !up) + { + charonservice->update_status(charonservice, + CHARONSERVICE_AUTH_ERROR); + return FALSE; + } + return TRUE; +} + +METHOD(listener_t, ike_state_change, bool, + private_android_service_t *this, ike_sa_t *ike_sa, ike_sa_state_t state) +{ + /* this call back is only registered during initiation */ + if (this->ike_sa == ike_sa && state == IKE_DESTROYING) + { + charonservice->update_status(charonservice, + CHARONSERVICE_UNREACHABLE_ERROR); + return FALSE; + } + return TRUE; +} + +METHOD(listener_t, alert, bool, + private_android_service_t *this, ike_sa_t *ike_sa, alert_t alert, + va_list args) +{ + if (this->ike_sa == ike_sa) + { + switch (alert) + { + case ALERT_PEER_ADDR_FAILED: + charonservice->update_status(charonservice, + CHARONSERVICE_LOOKUP_ERROR); + break; + case ALERT_PEER_AUTH_FAILED: + charonservice->update_status(charonservice, + CHARONSERVICE_PEER_AUTH_ERROR); + break; + default: + break; + } + } + return TRUE; +} + +METHOD(listener_t, ike_rekey, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + this->ike_sa = new; + } + return TRUE; +} + +static job_requeue_t initiate(private_android_service_t *this) +{ + identification_t *gateway, *user; + ike_cfg_t *ike_cfg; + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg; + traffic_selector_t *ts; + ike_sa_t *ike_sa; + auth_cfg_t *auth; + lifetime_cfg_t lifetime = { + .time = { + .life = 10800, /* 3h */ + .rekey = 10200, /* 2h50min */ + .jitter = 300 /* 5min */ + } + }; + + ike_cfg = ike_cfg_create(TRUE, TRUE, this->local_address, FALSE, + charon->socket->get_port(charon->socket, FALSE), + this->gateway, FALSE, IKEV2_UDP_PORT); + ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE)); + + peer_cfg = peer_cfg_create("android", IKEV2, ike_cfg, CERT_SEND_IF_ASKED, + UNIQUE_REPLACE, 1, /* keyingtries */ + 36000, 0, /* rekey 10h, reauth none */ + 600, 600, /* jitter, over 10min */ + TRUE, FALSE, /* mobike, aggressive */ + 0, 0, /* DPD delay, timeout */ + host_create_from_string("0.0.0.0", 0) /* virt */, + NULL, FALSE, NULL, NULL); /* pool, mediation */ + + + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); + user = identification_create_from_string(this->username); + auth->add(auth, AUTH_RULE_IDENTITY, user); + peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE); + auth = auth_cfg_create(); + auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); + gateway = identification_create_from_string(this->gateway); + auth->add(auth, AUTH_RULE_IDENTITY, gateway); + peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE); + + child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL, + ACTION_NONE, ACTION_NONE, ACTION_NONE, FALSE, + 0, 0, NULL, NULL, 0); + child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP)); + ts = traffic_selector_create_dynamic(0, 0, 65535); + child_cfg->add_traffic_selector(child_cfg, TRUE, ts); + ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0", + 0, "255.255.255.255", 65535); + child_cfg->add_traffic_selector(child_cfg, FALSE, ts); + peer_cfg->add_child_cfg(peer_cfg, child_cfg); + + /* get us an IKE_SA */ + ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager, + peer_cfg); + if (!ike_sa) + { + peer_cfg->destroy(peer_cfg); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return JOB_REQUEUE_NONE; + } + if (!ike_sa->get_peer_cfg(ike_sa)) + { + ike_sa->set_peer_cfg(ike_sa, peer_cfg); + } + peer_cfg->destroy(peer_cfg); + + /* store the IKE_SA so we can track its progress */ + this->ike_sa = ike_sa; + + /* get an additional reference because initiate consumes one */ + child_cfg->get_ref(child_cfg); + if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS) + { + DBG1(DBG_CFG, "failed to initiate tunnel"); + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, + ike_sa); + return JOB_REQUEUE_NONE; + } + charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); + return JOB_REQUEUE_NONE; +} + +METHOD(android_service_t, destroy, void, + private_android_service_t *this) +{ + charon->bus->remove_listener(charon->bus, &this->public.listener); + free(this->local_address); + free(this->username); + free(this->gateway); + free(this); +} + +/** + * See header + */ +android_service_t *android_service_create(char *local_address, char *gateway, + char *username) +{ + private_android_service_t *this; + + INIT(this, + .public = { + .listener = { + .ike_rekey = _ike_rekey, + .ike_updown = _ike_updown, + .ike_state_change = _ike_state_change, + .child_updown = _child_updown, + .alert = _alert, + }, + .destroy = _destroy, + }, + .local_address = local_address, + .username = username, + .gateway = gateway, + ); + + charon->bus->add_listener(charon->bus, &this->public.listener); + + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)initiate, this, + NULL, NULL)); + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.h b/src/frontends/android/jni/libandroidbridge/backend/android_service.h new file mode 100644 index 000000000..a7bd8b059 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010-2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup android_service android_service + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_SERVICE_H_ +#define ANDROID_SERVICE_H_ + +#include "android_creds.h" + +#include +#include + +typedef struct android_service_t android_service_t; + +/** + * Service that sets up an IKE_SA/CHILD_SA and handles events + */ +struct android_service_t { + + /** + * Implements listener_t. + */ + listener_t listener; + + /** + * Destroy a android_service_t. + */ + void (*destroy)(android_service_t *this); + +}; + +/** + * Create an Android service instance. Queues a job that starts initiation of a + * new IKE SA. + * + * @param local_address local ip address + * @param gateway gateway address + * @param username user name (local identity) + */ +android_service_t *android_service_create(char *local_address, char *gateway, + char *username); + +#endif /** ANDROID_SERVICE_H_ @}*/ From dffee9e2b0496ba2a5989e419be9131b37c245e7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 13:20:34 +0200 Subject: [PATCH 079/119] Helper function added that retrieves a local IP address --- .../android/logic/CharonVpnService.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 d66a5517d..6b454654a 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -17,9 +17,13 @@ package org.strongswan.android.logic; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Enumeration; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; @@ -399,6 +403,39 @@ public class CharonVpnService extends VpnService implements Runnable */ public native void deinitializeCharon(); + /** + * Helper function that retrieves a local IPv4 address. + * + * @return string representation of an IPv4 address, or null if none found + */ + private static String getLocalIPv4Address() + { + try + { + Enumeration en = NetworkInterface.getNetworkInterfaces(); + while (en.hasMoreElements()) + { + NetworkInterface intf = en.nextElement(); + + Enumeration enumIpAddr = intf.getInetAddresses(); + while (enumIpAddr.hasMoreElements()) + { + InetAddress inetAddress = enumIpAddr.nextElement(); + if (!inetAddress.isLoopbackAddress() && inetAddress.getAddress().length == 4) + { + return inetAddress.getHostAddress().toString(); + } + } + } + } + catch (SocketException ex) + { + ex.printStackTrace(); + return null; + } + return null; + } + /* * The libraries are extracted to /data/data/org.strongswan.android/... * during installation. From c6c39c783bd713fe2c159be7445a82f8b8c9c3e5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 13:23:41 +0200 Subject: [PATCH 080/119] Initiate an SA via native JNI method --- .../jni/libandroidbridge/charonservice.c | 63 +++++++++++++++++++ .../android/logic/CharonVpnService.java | 11 ++++ 2 files changed, 74 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 72feb9e96..8d595fe03 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -22,6 +22,7 @@ #include "charonservice.h" #include "android_jni.h" #include "backend/android_creds.h" +#include "backend/android_service.h" #include "kernel/android_ipsec.h" #include "kernel/android_net.h" @@ -50,6 +51,11 @@ struct private_charonservice_t { */ android_creds_t *creds; + /** + * android_service instance + */ + android_service_t *service; + /** * CharonVpnService reference */ @@ -195,6 +201,27 @@ failed: return NULL; } +/** + * Initiate a new connection + * + * @param local local ip address (gets owned) + * @param gateway gateway address (gets owned) + * @param username username (gets owned) + * @param password password (gets owned) + */ +static void initiate(char *local, char *gateway, char *username, char *password) +{ + private_charonservice_t *this = (private_charonservice_t*)charonservice; + + this->creds->clear(this->creds); + this->creds->add_username_password(this->creds, username, password); + memwipe(password, strlen(password)); + free(password); + + DESTROY_IF(this->service); + this->service = android_service_create(local, gateway, username); +} + /** * Initialize/deinitialize Android backend */ @@ -209,6 +236,11 @@ static bool charonservice_register(void *plugin, plugin_feature_t *feature, else { lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + if (this->service) + { + this->service->destroy(this->service); + this->service = NULL; + } } return TRUE; } @@ -341,3 +373,34 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void) library_deinit(); } +/** + * Convert a Java string to a C string. Memory is allocated. + */ +static inline char *convert_jstring(JNIEnv *env, jstring jstr) +{ + char *str; + jsize len; + + len = (*env)->GetStringUTFLength(env, jstr); + str = malloc(len + 1); + (*env)->GetStringUTFRegion(env, jstr, 0, len, str); + str[len] = '\0'; + return str; +} + +/** + * Initiate SA + */ +JNI_METHOD(CharonVpnService, initiate, void, + jstring jlocal_address, jstring jgateway, jstring jusername, + jstring jpassword) +{ + char *local_address, *gateway, *username, *password; + + local_address = convert_jstring(env, jlocal_address); + gateway = convert_jstring(env, jgateway); + username = convert_jstring(env, jusername); + password = convert_jstring(env, jpassword); + + initiate(local_address, gateway, username, password); +} 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 6b454654a..54ca247cc 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -194,6 +194,11 @@ public class CharonVpnService extends VpnService implements Runnable initializeCharon(); Log.i(TAG, "charon started"); + + String local_address = getLocalIPv4Address(); + initiate(local_address != null ? local_address : "0.0.0.0", + mCurrentProfile.getGateway(), mCurrentProfile.getUsername(), + mCurrentProfile.getPassword()); } } catch (InterruptedException ex) @@ -403,6 +408,12 @@ public class CharonVpnService extends VpnService implements Runnable */ public native void deinitializeCharon(); + /** + * Initiate VPN, provided by libandroidbridge.so + */ + public native void initiate(String local_address, String gateway, + String username, String password); + /** * Helper function that retrieves a local IPv4 address. * From 495e12aeadb02bfda8621984ff0a4e07e1fcd89f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:05:58 +0200 Subject: [PATCH 081/119] Add support for '+' in custom format specifiers --- src/libstrongswan/printf_hook.c | 2 ++ src/libstrongswan/printf_hook.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/libstrongswan/printf_hook.c b/src/libstrongswan/printf_hook.c index 2ae804380..8bd513c05 100644 --- a/src/libstrongswan/printf_hook.c +++ b/src/libstrongswan/printf_hook.c @@ -93,6 +93,7 @@ static int custom_print(FILE *stream, const struct printf_info *info, }; spec.hash = info->alt; + spec.plus = info->showsign; spec.minus = info->left; spec.width = info->width; @@ -164,6 +165,7 @@ static int custom_fmt_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *fmt_spec) } spec.hash = fmt_spec->fmt_hash; + spec.plus = fmt_spec->fmt_plus; spec.minus = fmt_spec->fmt_minus; spec.width = fmt_spec->fmt_field_width; diff --git a/src/libstrongswan/printf_hook.h b/src/libstrongswan/printf_hook.h index 93026cc34..96e5a19af 100644 --- a/src/libstrongswan/printf_hook.h +++ b/src/libstrongswan/printf_hook.h @@ -162,6 +162,11 @@ struct printf_hook_spec_t { */ int minus; + /** + * TRUE if a '+' was used in the format specifier + */ + int plus; + /** * The width as given in the format specifier. */ From f12b3ad2c90d8a3f68cf32eafeea8c61c26e7d2d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:06:59 +0200 Subject: [PATCH 082/119] Don't print hosts as %any if %+H is used That is, the plus sign can be used in the format string to force a numeric string representation of all host_t objects even 0.0.0.0 and :: which would otherwise be printed as %any and %any6. --- src/libstrongswan/utils/host.c | 2 +- src/libstrongswan/utils/host.h | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/libstrongswan/utils/host.c b/src/libstrongswan/utils/host.c index 0f40a0dd4..3a16138a4 100644 --- a/src/libstrongswan/utils/host.c +++ b/src/libstrongswan/utils/host.c @@ -110,7 +110,7 @@ int host_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, { snprintf(buffer, sizeof(buffer), "(null)"); } - else if (is_anyaddr(this)) + else if (is_anyaddr(this) && !spec->plus) { snprintf(buffer, sizeof(buffer), "%%any%s", this->address.sa_family == AF_INET6 ? "6" : ""); diff --git a/src/libstrongswan/utils/host.h b/src/libstrongswan/utils/host.h index 444878524..a8b010544 100644 --- a/src/libstrongswan/utils/host.h +++ b/src/libstrongswan/utils/host.h @@ -155,7 +155,7 @@ struct host_t { * * @param string string of an address, such as "152.96.193.130" * @param port port number - * @return host_t, NULL if string not an address. + * @return host_t, NULL if string not an address. */ host_t *host_create_from_string(char *string, u_int16_t port); @@ -165,7 +165,7 @@ host_t *host_create_from_string(char *string, u_int16_t port); * @param string hostname to resolve * @param family family to prefer, 0 for first match * @param port port number - * @return host_t, NULL lookup failed + * @return host_t, NULL lookup failed */ host_t *host_create_from_dns(char *string, int family, u_int16_t port); @@ -174,10 +174,10 @@ host_t *host_create_from_dns(char *string, int family, u_int16_t port); * * If family is AF_UNSPEC, it is guessed using address.len. * - * @param family Address family, such as AF_INET or AF_INET6 + * @param family Address family, such as AF_INET or AF_INET6 * @param address address as chunk_t in network order * @param port port number - * @return host_t, NULL if family not supported/chunk invalid + * @return host_t, NULL if family not supported/chunk invalid */ host_t *host_create_from_chunk(int family, chunk_t address, u_int16_t port); @@ -185,7 +185,7 @@ host_t *host_create_from_chunk(int family, chunk_t address, u_int16_t port); * Constructor to create a host_t object from a sockaddr struct * * @param sockaddr sockaddr struct which contains family, address and port - * @return host_t, NULL if family not supported + * @return host_t, NULL if family not supported */ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr); @@ -202,7 +202,7 @@ host_t *host_create_from_subnet(char *string, int *bits); * Create a host without an address, a "any" host. * * @param family family of the any host - * @return host_t, NULL if family not supported + * @return host_t, NULL if family not supported */ host_t *host_create_any(int family); @@ -212,6 +212,7 @@ host_t *host_create_any(int family); * Arguments are: * host_t *host * Use #-modifier to include port number + * Use +-modifier to force numeric representation (instead of e.g. %any) */ int host_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, const void *const *args); From 5215d512bf2b960fcf4816fdc27f4e3f2669fc58 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 13:45:49 +0200 Subject: [PATCH 083/119] Adapter class added around VpnService.Builder which allows to access it via JNI --- .../android/logic/CharonVpnService.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) 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 54ca247cc..2514642d1 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -37,6 +37,7 @@ import android.content.ServiceConnection; import android.net.VpnService; import android.os.Bundle; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.util.Log; public class CharonVpnService extends VpnService implements Runnable @@ -447,6 +448,104 @@ public class CharonVpnService extends VpnService implements Runnable return null; } + /** + * Adapter for VpnService.Builder which is used to access it safely via JNI. + */ + public class BuilderAdapter + { + VpnService.Builder builder; + + public BuilderAdapter(String name) + { + builder = new CharonVpnService.Builder(); + builder.setSession(name); + } + + public synchronized boolean addAddress(String address, int prefixLength) + { + try + { + builder.addAddress(address, prefixLength); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addDnsServer(String address) + { + try + { + builder.addDnsServer(address); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addRoute(String address, int prefixLength) + { + try + { + builder.addRoute(address, prefixLength); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean addSearchDomain(String domain) + { + try + { + builder.addSearchDomain(domain); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized boolean setMtu(int mtu) + { + try + { + builder.setMtu(mtu); + } + catch (IllegalArgumentException ex) + { + return false; + } + return true; + } + + public synchronized int establish() + { + ParcelFileDescriptor fd; + try + { + fd = builder.establish(); + } + catch (Exception ex) + { + ex.printStackTrace(); + return -1; + } + if (fd == null) + { + return -1; + } + return fd.detachFd(); + } + } + /* * The libraries are extracted to /data/data/org.strongswan.android/... * during installation. From ae4f1ea180af58704932572e970205b3d33f62c9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 13:48:54 +0200 Subject: [PATCH 084/119] Native counterpart of VpnService.Builder added, exposed by charonservice --- .../android/jni/libandroidbridge/Android.mk | 3 +- .../jni/libandroidbridge/android_jni.c | 4 + .../jni/libandroidbridge/android_jni.h | 1 + .../jni/libandroidbridge/charonservice.c | 21 +- .../jni/libandroidbridge/charonservice.h | 9 + .../jni/libandroidbridge/vpnservice_builder.c | 274 ++++++++++++++++++ .../jni/libandroidbridge/vpnservice_builder.h | 95 ++++++ .../android/logic/CharonVpnService.java | 8 +- 8 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 src/frontends/android/jni/libandroidbridge/vpnservice_builder.c create mode 100644 src/frontends/android/jni/libandroidbridge/vpnservice_builder.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index 9e1e94a8f..2e484ec5b 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -8,7 +8,8 @@ backend/android_creds.c backend/android_creds.h \ backend/android_service.c backend/android_service.h \ charonservice.c charonservice.h \ kernel/android_ipsec.c kernel/android_ipsec.h \ -kernel/android_net.c kernel/android_net.h +kernel/android_net.c kernel/android_net.h \ +vpnservice_builder.c vpnservice_builder.h # build libandroidbridge ------------------------------------------------------- diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.c b/src/frontends/android/jni/libandroidbridge/android_jni.c index b5e935a57..e7cb14fb7 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.c +++ b/src/frontends/android/jni/libandroidbridge/android_jni.c @@ -26,6 +26,7 @@ static JavaVM *android_jvm; jclass *android_charonvpnservice_class; +jclass *android_charonvpnservice_builder_class; /** * Thread-local variable. Only used because of the destructor @@ -88,6 +89,9 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) android_charonvpnservice_class = (*env)->NewGlobalRef(env, (*env)->FindClass(env, JNI_PACKAGE_STRING "/CharonVpnService")); + android_charonvpnservice_builder_class = + (*env)->NewGlobalRef(env, (*env)->FindClass(env, + JNI_PACKAGE_STRING "/CharonVpnService$BuilderAdapter")); return JNI_VERSION_1_6; } diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 427c641b4..39ba56fc1 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -43,6 +43,7 @@ * Initialized in JNI_OnLoad() */ extern jclass *android_charonvpnservice_class; +extern jclass *android_charonvpnservice_builder_class; /** * Attach the current thread to the JVM diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 8d595fe03..772a96f69 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -56,6 +56,11 @@ struct private_charonservice_t { */ android_service_t *service; + /** + * VpnService builder (accessed via JNI) + */ + vpnservice_builder_t *builder; + /** * CharonVpnService reference */ @@ -201,6 +206,12 @@ failed: return NULL; } +METHOD(charonservice_t, get_vpnservice_builder, vpnservice_builder_t*, + private_charonservice_t *this) +{ + return this->builder; +} + /** * Initiate a new connection * @@ -248,7 +259,7 @@ static bool charonservice_register(void *plugin, plugin_feature_t *feature, /** * Initialize the charonservice object */ -static void charonservice_init(JNIEnv *env, jobject service) +static void charonservice_init(JNIEnv *env, jobject service, jobject builder) { private_charonservice_t *this; static plugin_feature_t features[] = { @@ -266,8 +277,10 @@ static void charonservice_init(JNIEnv *env, jobject service) .update_status = _update_status, .bypass_socket = _bypass_socket, .get_trusted_certificates = _get_trusted_certificates, + .get_vpnservice_builder = _get_vpnservice_builder, }, .creds = android_creds_create(), + .builder = vpnservice_builder_create(builder), .vpn_service = (*env)->NewGlobalRef(env, service), ); charonservice = &this->public; @@ -286,6 +299,7 @@ static void charonservice_deinit(JNIEnv *env) { private_charonservice_t *this = (private_charonservice_t*)charonservice; + this->builder->destroy(this->builder); this->creds->destroy(this->creds); (*env)->DeleteGlobalRef(env, this->vpn_service); free(this); @@ -305,7 +319,8 @@ static void segv_handler(int signal) /** * Initialize charon and the libraries via JNI */ -JNI_METHOD(CharonVpnService, initializeCharon, void) +JNI_METHOD(CharonVpnService, initializeCharon, void, + jobject builder) { struct sigaction action; @@ -334,7 +349,7 @@ JNI_METHOD(CharonVpnService, initializeCharon, void) return; } - charonservice_init(env, this); + charonservice_init(env, this, builder); if (!libcharon_init("charon") || !charon->initialize(charon, PLUGINS)) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.h b/src/frontends/android/jni/libandroidbridge/charonservice.h index e538a22c9..706eaa220 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.h +++ b/src/frontends/android/jni/libandroidbridge/charonservice.h @@ -31,6 +31,8 @@ #ifndef CHARONSERVICE_H_ #define CHARONSERVICE_H_ +#include "vpnservice_builder.h" + #include #include @@ -83,6 +85,13 @@ struct charonservice_t { */ linked_list_t *(*get_trusted_certificates)(charonservice_t *this); + /** + * Get the current vpnservice_builder_t object + * + * @return VpnService.Builder instance + */ + vpnservice_builder_t *(*get_vpnservice_builder)(charonservice_t *this); + }; /** diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c new file mode 100644 index 000000000..6ff732520 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "vpnservice_builder.h" +#include "android_jni.h" + +#include +#include + +typedef struct private_vpnservice_builder_t private_vpnservice_builder_t; + +/** + * private data of vpnservice_builder + */ +struct private_vpnservice_builder_t { + + /** + * public interface + */ + vpnservice_builder_t public; + + /** + * Java object + */ + jobject builder; +}; + +METHOD(vpnservice_builder_t, add_address, bool, + private_vpnservice_builder_t *this, host_t *addr) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding interface address %H", addr); + + if (addr->get_family(addr) != AF_INET) + { + goto failed; + } + if (snprintf(buf, sizeof(buf), "%H", addr) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addAddress", "(Ljava/lang/String;I)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, 32)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add address"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, set_mtu, bool, + private_vpnservice_builder_t *this, int mtu) +{ + JNIEnv *env; + jmethodID method_id; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: setting MTU to %d", mtu); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "setMtu", "(I)Z"); + if (!method_id) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, mtu)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to set MTU"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, add_route, bool, + private_vpnservice_builder_t *this, host_t *net, int prefix) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding route %+H/%d", net, prefix); + + if (net->get_family(net) != AF_INET) + { + goto failed; + } + if (snprintf(buf, sizeof(buf), "%+H", net) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addRoute", "(Ljava/lang/String;I)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str, prefix)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add route"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, add_dns, bool, + private_vpnservice_builder_t *this, host_t *dns) +{ + JNIEnv *env; + jmethodID method_id; + jstring str; + char buf[INET_ADDRSTRLEN]; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: adding DNS server %H", dns); + + if (dns->get_family(dns) != AF_INET) + { + goto failed; + } + if (snprintf(buf, sizeof(buf), "%H", dns) >= sizeof(buf)) + { + goto failed; + } + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "addDnsServer", "(Ljava/lang/String;)Z"); + if (!method_id) + { + goto failed; + } + str = (*env)->NewStringUTF(env, buf); + if (!str) + { + goto failed; + } + if (!(*env)->CallBooleanMethod(env, this->builder, method_id, str)) + { + goto failed; + } + androidjni_detach_thread(); + return TRUE; + +failed: + DBG1(DBG_LIB, "builder: failed to add DNS server"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return FALSE; +} + +METHOD(vpnservice_builder_t, establish, int, + private_vpnservice_builder_t *this) +{ + JNIEnv *env; + jmethodID method_id; + int fd; + + androidjni_attach_thread(&env); + + DBG2(DBG_LIB, "builder: building TUN device"); + + method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, + "establish", "()I"); + if (!method_id) + { + goto failed; + } + fd = (*env)->CallIntMethod(env, this->builder, method_id); + if (fd == -1) + { + goto failed; + } + androidjni_detach_thread(); + return fd; + +failed: + DBG1(DBG_LIB, "builder: failed to build TUN device"); + androidjni_exception_occurred(env); + androidjni_detach_thread(); + return -1; +} + +METHOD(vpnservice_builder_t, destroy, void, + private_vpnservice_builder_t *this) +{ + JNIEnv *env; + + androidjni_attach_thread(&env); + (*env)->DeleteGlobalRef(env, this->builder); + androidjni_detach_thread(); + free(this); +} + +vpnservice_builder_t *vpnservice_builder_create(jobject builder) +{ + JNIEnv *env; + private_vpnservice_builder_t *this; + + INIT(this, + .public = { + .add_address = _add_address, + .add_route = _add_route, + .add_dns = _add_dns, + .set_mtu = _set_mtu, + .establish = _establish, + .destroy = _destroy, + }, + ); + + androidjni_attach_thread(&env); + this->builder = (*env)->NewGlobalRef(env, builder); + androidjni_detach_thread(); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h new file mode 100644 index 000000000..82efd05f7 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup vpnservice_builder vpnservice_builder + * @{ @ingroup libandroidbridge + */ + +#ifndef VPNSERVICE_BUILDER_H_ +#define VPNSERVICE_BUILDER_H_ + +#include + +#include +#include + +typedef struct vpnservice_builder_t vpnservice_builder_t; + +/** + * VpnService.Builder, used to build a TUN device. + * + * Communicates with CharonVpnService.BuilderAdapter via JNI + */ +struct vpnservice_builder_t { + + /** + * Add an interface address + * + * @param addr the desired interface address + * @return TRUE on success + */ + bool (*add_address)(vpnservice_builder_t *this, host_t *addr); + + /** + * Add a route + * + * @param net the network address + * @param prefix_length the prefix length + * @return TRUE on success + */ + bool (*add_route)(vpnservice_builder_t *this, host_t *net, int prefix); + + /** + * Add a DNS server + * + * @param dns the address of the DNS server + * @return TRUE on success + */ + bool (*add_dns)(vpnservice_builder_t *this, host_t *dns); + + /** + * Set the MTU for the TUN device + * + * @param mtu the MTU to set + * @return TRUE on success + */ + bool (*set_mtu)(vpnservice_builder_t *this, int mtu); + + /** + * Build the TUN device + * + * @return the TUN file descriptor, -1 if failed + */ + int (*establish)(vpnservice_builder_t *this); + + /** + * Destroy a vpnservice_builder + */ + void (*destroy)(vpnservice_builder_t *this); + +}; + +/** + * Create a vpnservice_builder instance + * + * @param builder CharonVpnService.BuilderAdapter object + * @return vpnservice_builder_t instance + */ +vpnservice_builder_t *vpnservice_builder_create(jobject builder); + +#endif /** VPNSERVICE_BUILDER_H_ @}*/ 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 2514642d1..1d9881cb8 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -193,7 +193,8 @@ public class CharonVpnService extends VpnService implements Runnable setError(ErrorState.NO_ERROR); setState(State.CONNECTING); - initializeCharon(); + BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName()); + initializeCharon(builder); Log.i(TAG, "charon started"); String local_address = getLocalIPv4Address(); @@ -401,8 +402,10 @@ public class CharonVpnService extends VpnService implements Runnable /** * Initialization of charon, provided by libandroidbridge.so + * + * @param builder BuilderAdapter for this connection */ - public native void initializeCharon(); + public native void initializeCharon(BuilderAdapter builder); /** * Deinitialize charon, provided by libandroidbridge.so @@ -450,6 +453,7 @@ public class CharonVpnService extends VpnService implements Runnable /** * Adapter for VpnService.Builder which is used to access it safely via JNI. + * There is a corresponding C object to access it from native code. */ public class BuilderAdapter { From 3a05756b423411a8dfba6c68adc4c85cbf1ec353 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:04:14 +0200 Subject: [PATCH 085/119] An Android specific attribute handler installs DNS servers via Builder --- .../android/jni/libandroidbridge/Android.mk | 1 + .../libandroidbridge/backend/android_attr.c | 120 ++++++++++++++++++ .../libandroidbridge/backend/android_attr.h | 52 ++++++++ .../jni/libandroidbridge/charonservice.c | 12 ++ 4 files changed, 185 insertions(+) create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_attr.c create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_attr.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index 2e484ec5b..e1806f702 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -4,6 +4,7 @@ include $(CLEAR_VARS) # copy-n-paste from Makefile.am LOCAL_SRC_FILES := \ android_jni.c android_jni.h \ +backend/android_attr.c backend/android_attr.h \ backend/android_creds.c backend/android_creds.h \ backend/android_service.c backend/android_service.h \ charonservice.c charonservice.h \ diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_attr.c b/src/frontends/android/jni/libandroidbridge/backend/android_attr.c new file mode 100644 index 000000000..e8c506950 --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_attr.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "android_attr.h" +#include "../charonservice.h" + +#include +#include +#include + +typedef struct private_android_attr_t private_android_attr_t; + +/** + * Private data of an android_attr_t object. + */ +struct private_android_attr_t { + + /** + * Public interface. + */ + android_attr_t public; +}; + +METHOD(attribute_handler_t, handle, bool, + private_android_attr_t *this, identification_t *server, + configuration_attribute_type_t type, chunk_t data) +{ + vpnservice_builder_t *builder; + host_t *dns; + + switch (type) + { + case INTERNAL_IP4_DNS: + dns = host_create_from_chunk(AF_INET, data, 0); + break; + default: + return FALSE; + } + + if (!dns || dns->is_anyaddr(dns)) + { + DESTROY_IF(dns); + return FALSE; + } + + builder = charonservice->get_vpnservice_builder(charonservice); + builder->add_dns(builder, dns); + dns->destroy(dns); + return TRUE; +} + +METHOD(attribute_handler_t, release, void, + private_android_attr_t *this, identification_t *server, + configuration_attribute_type_t type, chunk_t data) +{ + /* DNS servers cannot be removed from an existing TUN device */ +} + +METHOD(enumerator_t, enumerate_dns, bool, + enumerator_t *this, configuration_attribute_type_t *type, chunk_t *data) +{ + *type = INTERNAL_IP4_DNS; + *data = chunk_empty; + this->enumerate = (void*)return_false; + return TRUE; +} + +METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t*, + private_android_attr_t *this, identification_t *server, host_t *vip) +{ + enumerator_t *enumerator; + + INIT(enumerator, + .enumerate = (void*)_enumerate_dns, + .destroy = (void*)free, + ); + return enumerator; +} + +METHOD(android_attr_t, destroy, void, + private_android_attr_t *this) +{ + free(this); +} + +/** + * Described in header + */ +android_attr_t *android_attr_create() +{ + private_android_attr_t *this; + + INIT(this, + .public = { + .handler = { + .handle = _handle, + .release = _release, + .create_attribute_enumerator = _create_attribute_enumerator, + }, + .destroy = _destroy, + }, + ); + + return &this->public; +} + diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_attr.h b/src/frontends/android/jni/libandroidbridge/backend/android_attr.h new file mode 100644 index 000000000..56b02e1ce --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_attr.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup android_attr android_attr + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_ATTR_H_ +#define ANDROID_ATTR_H_ + +#include +#include + +typedef struct android_attr_t android_attr_t; + +/** + * Handler for DNS configuration + */ +struct android_attr_t { + + /** + * implements the attribute_handler_t interface + */ + attribute_handler_t handler; + + /** + * Destroy a android_attr_t + */ + void (*destroy)(android_attr_t *this); +}; + +/** + * Create a android_attr_t instance. + */ +android_attr_t *android_attr_create(void); + +#endif /** ANDROID_ATTR_H_ @}*/ + diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 772a96f69..94ed8f6b6 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -21,6 +21,7 @@ #include "charonservice.h" #include "android_jni.h" +#include "backend/android_attr.h" #include "backend/android_creds.h" #include "backend/android_service.h" #include "kernel/android_ipsec.h" @@ -46,6 +47,11 @@ struct private_charonservice_t { */ charonservice_t public; + /** + * android_attr instance + */ + android_attr_t *attr; + /** * android_creds instance */ @@ -243,10 +249,14 @@ static bool charonservice_register(void *plugin, plugin_feature_t *feature, if (reg) { lib->credmgr->add_set(lib->credmgr, &this->creds->set); + hydra->attributes->add_handler(hydra->attributes, + &this->attr->handler); } else { lib->credmgr->remove_set(lib->credmgr, &this->creds->set); + hydra->attributes->remove_handler(hydra->attributes, + &this->attr->handler); if (this->service) { this->service->destroy(this->service); @@ -279,6 +289,7 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder) .get_trusted_certificates = _get_trusted_certificates, .get_vpnservice_builder = _get_vpnservice_builder, }, + .attr = android_attr_create(), .creds = android_creds_create(), .builder = vpnservice_builder_create(builder), .vpn_service = (*env)->NewGlobalRef(env, service), @@ -301,6 +312,7 @@ static void charonservice_deinit(JNIEnv *env) this->builder->destroy(this->builder); this->creds->destroy(this->creds); + this->attr->destroy(this->attr); (*env)->DeleteGlobalRef(env, this->vpn_service); free(this); charonservice = NULL; From a2993d72435970805f271231bf169e9c5a092508 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:43:39 +0200 Subject: [PATCH 086/119] Create a TUN device via VpnService.Builder once the CHILD_SA is established --- .../backend/android_service.c | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 5c18924be..691d5fc6d 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -17,13 +17,17 @@ #include "android_service.h" #include "../charonservice.h" +#include "../vpnservice_builder.h" #include #include #include +#include typedef struct private_android_service_t private_android_service_t; +#define TUN_DEFAULT_MTU 1400 + /** * private data of Android service */ @@ -54,8 +58,72 @@ struct private_android_service_t { */ char *username; + /** + * lock to safely access the TUN device fd + */ + rwlock_t *lock; + + /** + * TUN device file descriptor + */ + int tunfd; + }; +/** + * Setup a new TUN device for the supplied SAs. + * Additional information such as DNS servers are gathered in appropriate + * listeners asynchronously. To be sure every required bit of information is + * available this should be called after the CHILD_SA has been established. + */ +static bool setup_tun_device(private_android_service_t *this, + ike_sa_t *ike_sa, child_sa_t *child_sa) +{ + vpnservice_builder_t *builder; + int tunfd; + + DBG1(DBG_DMN, "setting up TUN device for CHILD_SA %s{%u}", + child_sa->get_name(child_sa), child_sa->get_reqid(child_sa)); + + builder = charonservice->get_vpnservice_builder(charonservice); + if (!builder->set_mtu(builder, TUN_DEFAULT_MTU)) + { + return FALSE; + } + + tunfd = builder->establish(builder); + if (tunfd == -1) + { + return FALSE; + } + + this->lock->write_lock(this->lock); + this->tunfd = tunfd; + this->lock->unlock(this->lock); + + DBG1(DBG_DMN, "successfully created TUN device"); + return TRUE; +} + +/** + * Close the current tun device + */ +static void close_tun_device(private_android_service_t *this) +{ + int tunfd; + + this->lock->write_lock(this->lock); + if (this->tunfd < 0) + { /* already closed (or never created) */ + this->lock->unlock(this->lock); + return; + } + tunfd = this->tunfd; + this->tunfd = -1; + this->lock->unlock(this->lock); + close(tunfd); +} + METHOD(listener_t, child_updown, bool, private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, bool up) @@ -67,11 +135,20 @@ METHOD(listener_t, child_updown, bool, /* disable the hooks registered to catch initiation failures */ this->public.listener.ike_updown = NULL; this->public.listener.ike_state_change = NULL; + if (!setup_tun_device(this, ike_sa, child_sa)) + { + DBG1(DBG_DMN, "failed to setup TUN device"); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + return FALSE; + + } charonservice->update_status(charonservice, CHARONSERVICE_CHILD_STATE_UP); } else { + close_tun_device(this); charonservice->update_status(charonservice, CHARONSERVICE_CHILD_STATE_DOWN); return FALSE; @@ -230,6 +307,9 @@ METHOD(android_service_t, destroy, void, private_android_service_t *this) { charon->bus->remove_listener(charon->bus, &this->public.listener); + /* make sure the tun device is actually closed */ + close_tun_device(this); + this->lock->destroy(this->lock); free(this->local_address); free(this->username); free(this->gateway); @@ -255,9 +335,11 @@ android_service_t *android_service_create(char *local_address, char *gateway, }, .destroy = _destroy, }, + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .local_address = local_address, .username = username, .gateway = gateway, + .tunfd = -1, ); charon->bus->add_listener(charon->bus, &this->public.listener); From 62e6630b248b0f7e387dae8d16dd56db71262b35 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:46:22 +0200 Subject: [PATCH 087/119] Add virtual IP to the TUN device builder After the CHILD_SA is established we can easily get this address from the IKE_SA. --- .../jni/libandroidbridge/backend/android_service.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 691d5fc6d..d44bebc9a 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -80,13 +80,21 @@ static bool setup_tun_device(private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa) { vpnservice_builder_t *builder; + host_t *vip; int tunfd; DBG1(DBG_DMN, "setting up TUN device for CHILD_SA %s{%u}", child_sa->get_name(child_sa), child_sa->get_reqid(child_sa)); + vip = ike_sa->get_virtual_ip(ike_sa, TRUE); + if (!vip || vip->is_anyaddr(vip)) + { + DBG1(DBG_DMN, "setting up TUN device failed, no virtual IP found"); + return FALSE; + } builder = charonservice->get_vpnservice_builder(charonservice); - if (!builder->set_mtu(builder, TUN_DEFAULT_MTU)) + if (!builder->add_address(builder, vip) || + !builder->set_mtu(builder, TUN_DEFAULT_MTU)) { return FALSE; } From 30ba2ff7771e84a8ac7d08089945b1e6fa5cca38 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:47:47 +0200 Subject: [PATCH 088/119] Add routes based on the installed IPsec policies to the TUN device builder --- .../backend/android_service.c | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index d44bebc9a..40ca86ac5 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -70,6 +70,51 @@ struct private_android_service_t { }; +/** + * Add a route to the TUN device builder + */ +static bool add_route(vpnservice_builder_t *builder, host_t *net, + u_int8_t prefix) +{ + /* if route is 0.0.0.0/0, split it into two routes 0.0.0.0/1 and + * 128.0.0.0/1 because otherwise it would conflict with the current default + * route */ + if (net->is_anyaddr(net) && prefix == 0) + { + bool success; + + success = add_route(builder, net, 1); + net = host_create_from_string("128.0.0.0", 0); + success = success && add_route(builder, net, 1); + net->destroy(net); + return success; + } + return builder->add_route(builder, net, prefix); +} + +/** + * Generate and set routes from installed IPsec policies + */ +static bool add_routes(vpnservice_builder_t *builder, child_sa_t *child_sa) +{ + traffic_selector_t *src_ts, *dst_ts; + enumerator_t *enumerator; + bool success = TRUE; + + enumerator = child_sa->create_policy_enumerator(child_sa); + while (success && enumerator->enumerate(enumerator, &src_ts, &dst_ts)) + { + host_t *net; + u_int8_t prefix; + + dst_ts->to_subnet(dst_ts, &net, &prefix); + success = add_route(builder, net, prefix); + net->destroy(net); + } + enumerator->destroy(enumerator); + return success; +} + /** * Setup a new TUN device for the supplied SAs. * Additional information such as DNS servers are gathered in appropriate @@ -94,6 +139,7 @@ static bool setup_tun_device(private_android_service_t *this, builder = charonservice->get_vpnservice_builder(charonservice); if (!builder->add_address(builder, vip) || + !add_routes(builder, child_sa) || !builder->set_mtu(builder, TUN_DEFAULT_MTU)) { return FALSE; From 3b3cf0c87ae5a73fad48933ff070f4b8ec919d98 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:49:52 +0200 Subject: [PATCH 089/119] Add simple callbacks to receive/send ESP packets via libipsec/receiver. --- .../backend/android_service.c | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 40ca86ac5..bd28e7de9 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -70,6 +71,25 @@ struct private_android_service_t { }; +/** + * Outbound callback + */ +static void send_esp(void *data, esp_packet_t *packet) +{ + charon->sender->send_no_marker(charon->sender, (packet_t*)packet); +} + +/** + * Receiver callback + */ +static void receiver_esp_cb(void *data, packet_t *packet) +{ + esp_packet_t *esp_packet; + + esp_packet = esp_packet_create_from_packet(packet); + ipsec->processor->queue_inbound(ipsec->processor, esp_packet); +} + /** * Add a route to the TUN device builder */ @@ -156,6 +176,12 @@ static bool setup_tun_device(private_android_service_t *this, this->lock->unlock(this->lock); DBG1(DBG_DMN, "successfully created TUN device"); + + charon->receiver->add_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb, NULL); + ipsec->processor->register_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp, NULL); + return TRUE; } @@ -175,6 +201,11 @@ static void close_tun_device(private_android_service_t *this) tunfd = this->tunfd; this->tunfd = -1; this->lock->unlock(this->lock); + + ipsec->processor->unregister_outbound(ipsec->processor, + (ipsec_outbound_cb_t)send_esp); + charon->receiver->del_esp_cb(charon->receiver, + (receiver_esp_cb_t)receiver_esp_cb); close(tunfd); } From d9531100fac082a803b2d2d4a7505a638ab1d3dd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:51:59 +0200 Subject: [PATCH 090/119] Added a handler that writes inbound plain text packets to the TUN device --- .../backend/android_service.c | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index bd28e7de9..417a8aa70 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -15,6 +15,8 @@ * for more details. */ +#include + #include "android_service.h" #include "../charonservice.h" #include "../vpnservice_builder.h" @@ -79,6 +81,35 @@ static void send_esp(void *data, esp_packet_t *packet) charon->sender->send_no_marker(charon->sender, (packet_t*)packet); } +/** + * Inbound callback + */ +static void deliver_plain(private_android_service_t *this, + ip_packet_t *packet) +{ + chunk_t encoding; + ssize_t len; + + encoding = packet->get_encoding(packet); + + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* the TUN device is already closed */ + this->lock->unlock(this->lock); + packet->destroy(packet); + return; + } + len = write(this->tunfd, encoding.ptr, encoding.len); + this->lock->unlock(this->lock); + + if (len < 0 || len != encoding.len) + { + DBG1(DBG_DMN, "failed to write packet to TUN device: %s", + strerror(errno)); + } + packet->destroy(packet); +} + /** * Receiver callback */ @@ -179,6 +210,8 @@ static bool setup_tun_device(private_android_service_t *this, charon->receiver->add_esp_cb(charon->receiver, (receiver_esp_cb_t)receiver_esp_cb, NULL); + ipsec->processor->register_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain, this); ipsec->processor->register_outbound(ipsec->processor, (ipsec_outbound_cb_t)send_esp, NULL); @@ -204,6 +237,8 @@ static void close_tun_device(private_android_service_t *this) ipsec->processor->unregister_outbound(ipsec->processor, (ipsec_outbound_cb_t)send_esp); + ipsec->processor->unregister_inbound(ipsec->processor, + (ipsec_inbound_cb_t)deliver_plain); charon->receiver->del_esp_cb(charon->receiver, (receiver_esp_cb_t)receiver_esp_cb); close(tunfd); From 2483f6a4e0eee7ea507c5dbbad0c2cdff49e13d6 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 14:54:44 +0200 Subject: [PATCH 091/119] Job added which handles plain text packets read from TUN device --- .../backend/android_service.c | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 417a8aa70..dfc0d2342 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -15,6 +15,7 @@ * for more details. */ +#include #include #include "android_service.h" @@ -26,6 +27,7 @@ #include #include #include +#include typedef struct private_android_service_t private_android_service_t; @@ -121,6 +123,62 @@ static void receiver_esp_cb(void *data, packet_t *packet) ipsec->processor->queue_inbound(ipsec->processor, esp_packet); } +/** + * Job handling outbound plaintext packets + */ +static job_requeue_t handle_plain(private_android_service_t *this) +{ + ip_packet_t *packet; + chunk_t raw; + fd_set set; + ssize_t len; + int tunfd; + bool old; + + FD_ZERO(&set); + + this->lock->read_lock(this->lock); + if (this->tunfd < 0) + { /* the TUN device is already closed */ + this->lock->unlock(this->lock); + return JOB_REQUEUE_NONE; + } + tunfd = this->tunfd; + FD_SET(tunfd, &set); + this->lock->unlock(this->lock); + + old = thread_cancelability(TRUE); + len = select(tunfd + 1, &set, NULL, NULL, NULL); + thread_cancelability(old); + + if (len < 0) + { + DBG1(DBG_DMN, "select on TUN device failed: %s", strerror(errno)); + return JOB_REQUEUE_NONE; + } + + raw = chunk_alloc(TUN_DEFAULT_MTU); + len = read(tunfd, raw.ptr, raw.len); + if (len < 0) + { + DBG1(DBG_DMN, "reading from TUN device failed: %s", strerror(errno)); + chunk_free(&raw); + return JOB_REQUEUE_FAIR; + } + raw.len = len; + + packet = ip_packet_create(raw); + if (packet) + { + ipsec->processor->queue_outbound(ipsec->processor, packet); + } + else + { + DBG1(DBG_DMN, "invalid IP packet read from TUN device"); + } + return JOB_REQUEUE_DIRECT; +} + /** * Add a route to the TUN device builder */ @@ -167,7 +225,8 @@ static bool add_routes(vpnservice_builder_t *builder, child_sa_t *child_sa) } /** - * Setup a new TUN device for the supplied SAs. + * Setup a new TUN device for the supplied SAs, also queues a job that + * reads packets from this device. * Additional information such as DNS servers are gathered in appropriate * listeners asynchronously. To be sure every required bit of information is * available this should be called after the CHILD_SA has been established. @@ -215,6 +274,9 @@ static bool setup_tun_device(private_android_service_t *this, ipsec->processor->register_outbound(ipsec->processor, (ipsec_outbound_cb_t)send_esp, NULL); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, + NULL, (callback_job_cancel_t)return_false)); return TRUE; } From 76e55491eb3f3b7c82ae68444eda1b3ecc524eb1 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 15:02:34 +0200 Subject: [PATCH 092/119] Reduce number of retransmits on Android --- .../android/jni/libandroidbridge/charonservice.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 94ed8f6b6..7266f02df 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -34,6 +34,9 @@ #include #define ANDROID_DEBUG_LEVEL 1 +#define ANDROID_RETRASNMIT_TRIES 3 +#define ANDROID_RETRANSMIT_TIMEOUT 3.0 +#define ANDROID_RETRANSMIT_BASE 1.4 typedef struct private_charonservice_t private_charonservice_t; @@ -301,6 +304,12 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder) lib->settings->set_int(lib->settings, "charon.plugins.android_log.loglevel", ANDROID_DEBUG_LEVEL); + lib->settings->set_int(lib->settings, + "charon.retransmit_tries", ANDROID_RETRASNMIT_TRIES); + lib->settings->set_double(lib->settings, + "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); + lib->settings->set_double(lib->settings, + "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); } /** From 644db4d7c5f3d36303a453ff553a49bec97deef4 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 15:03:00 +0200 Subject: [PATCH 093/119] Close IKE_SA on Android immediately if setting up CHILD_SA fails --- src/frontends/android/jni/libandroidbridge/charonservice.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 7266f02df..ce1e8497b 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -310,6 +310,8 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder) "charon.retransmit_timeout", ANDROID_RETRANSMIT_TIMEOUT); lib->settings->set_double(lib->settings, "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); + lib->settings->set_bool(lib->settings, + "charon.close_ike_on_child_failure", TRUE); } /** From a39a301a126e3361684ebe02c6c1223a3e38ccdb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 15:50:36 +0200 Subject: [PATCH 094/119] Don't set the source address on Android --- .../android/jni/libandroidbridge/charonservice.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index ce1e8497b..3a5d0cb1e 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -312,6 +312,15 @@ static void charonservice_init(JNIEnv *env, jobject service, jobject builder) "charon.retransmit_base", ANDROID_RETRANSMIT_BASE); lib->settings->set_bool(lib->settings, "charon.close_ike_on_child_failure", TRUE); + /* setting the source address breaks the VpnService.protect() function which + * uses SO_BINDTODEVICE internally. the addresses provided to the kernel as + * auxiliary data have precedence over this option causing a routing loop if + * the gateway is contained in the VPN routes. alternatively, providing an + * explicit device (in addition or instead of the source address) in the + * auxiliary data would also work, but we currently don't have that + * information */ + lib->settings->set_bool(lib->settings, + "charon.plugins.socket-default.set_source", FALSE); } /** From 9d0f8a3a95881d4fe3c796dab2eaff6d04e9a887 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 8 Aug 2012 19:10:33 +0200 Subject: [PATCH 095/119] Use a separate (volatile) variable for certificate alias If a connection is started while certificates are still loading and the initiation is then canceled a deadlock could result if the daemon is trying to enumerate the certificates just then. --- .../org/strongswan/android/logic/CharonVpnService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 1d9881cb8..58730c008 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -46,6 +46,7 @@ public class CharonVpnService extends VpnService implements Runnable private VpnProfileDataSource mDataSource; private Thread mConnectionHandler; private VpnProfile mCurrentProfile; + private volatile String mCurrentCertificateAlias; private VpnProfile mNextProfile; private volatile boolean mProfileUpdated; private volatile boolean mTerminate; @@ -189,6 +190,10 @@ public class CharonVpnService extends VpnService implements Runnable mCurrentProfile = mNextProfile; mNextProfile = null; + /* store this in a separate (volatile) variable to avoid + * a possible deadlock during deinitialization */ + mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias(); + setProfile(mCurrentProfile); setError(ErrorState.NO_ERROR); setState(State.CONNECTING); @@ -350,7 +355,7 @@ public class CharonVpnService extends VpnService implements Runnable * @param hash optional alias (only hash part), if given matching certificates are returned * @return a list of DER encoded CA certificates */ - private synchronized byte[][] getTrustedCertificates(String hash) + private byte[][] getTrustedCertificates(String hash) { ArrayList certs = new ArrayList(); TrustedCertificateManager certman = TrustedCertificateManager.getInstance(); @@ -373,7 +378,7 @@ public class CharonVpnService extends VpnService implements Runnable } else { - String alias = this.mCurrentProfile.getCertificateAlias(); + String alias = this.mCurrentCertificateAlias; if (alias != null) { X509Certificate cert = certman.getCACertificateFromAlias(alias); From e7908526fda0c4a17580ec304e717ef20f800617 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:11:32 +0200 Subject: [PATCH 096/119] Add a fragment to MainActivity which will display the current VPN state The fragment is bound to the VpnStateService and registered as listener. --- .../res/drawable/vpn_state_background.xml | 21 +++++ src/frontends/android/res/layout/main.xml | 13 ++- .../android/res/layout/vpn_state_fragment.xml | 30 ++++++ .../android/ui/VpnStateFragment.java | 93 +++++++++++++++++++ 4 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 src/frontends/android/res/drawable/vpn_state_background.xml create mode 100644 src/frontends/android/res/layout/vpn_state_fragment.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java diff --git a/src/frontends/android/res/drawable/vpn_state_background.xml b/src/frontends/android/res/drawable/vpn_state_background.xml new file mode 100644 index 000000000..24f469add --- /dev/null +++ b/src/frontends/android/res/drawable/vpn_state_background.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/src/frontends/android/res/layout/main.xml b/src/frontends/android/res/layout/main.xml index 104a26d03..1c7973e20 100644 --- a/src/frontends/android/res/layout/main.xml +++ b/src/frontends/android/res/layout/main.xml @@ -14,14 +14,21 @@ for more details. --> + + + android:layout_height="0dp" + android:layout_weight="1" /> diff --git a/src/frontends/android/res/layout/vpn_state_fragment.xml b/src/frontends/android/res/layout/vpn_state_fragment.xml new file mode 100644 index 000000000..c3adeae8a --- /dev/null +++ b/src/frontends/android/res/layout/vpn_state_fragment.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java new file mode 100644 index 000000000..5c4ffdd5d --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import org.strongswan.android.R; +import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.VpnStateListener; + +import android.app.Fragment; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class VpnStateFragment extends Fragment implements VpnStateListener +{ + private VpnStateService mService; + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName name) + { + mService = null; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + mService = ((VpnStateService.LocalBinder)service).getService(); + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + /* bind to the service only seems to work from the ApplicationContext */ + Context context = getActivity().getApplicationContext(); + context.bindService(new Intent(context, VpnStateService.class), + mServiceConnection, Service.BIND_AUTO_CREATE); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.vpn_state_fragment, null); + return view; + } + + @Override + public void onStop() + { + super.onStop(); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + if (mService != null) + { + mService.unregisterListener(this); + getActivity().getApplicationContext().unbindService(mServiceConnection); + } + } + + @Override + public void stateChanged() + { + } +} From a43bdf9a37a3ba60c33160b57fb8db613b2390e9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:22:12 +0200 Subject: [PATCH 097/119] Show current VPN state and profile name Show modal dialogs while connecting and disconnecting the VPN. --- .../android/res/layout/vpn_state_fragment.xml | 50 +++++++ src/frontends/android/res/values/strings.xml | 10 ++ .../android/ui/VpnStateFragment.java | 129 ++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/src/frontends/android/res/layout/vpn_state_fragment.xml b/src/frontends/android/res/layout/vpn_state_fragment.xml index c3adeae8a..12d890a90 100644 --- a/src/frontends/android/res/layout/vpn_state_fragment.xml +++ b/src/frontends/android/res/layout/vpn_state_fragment.xml @@ -22,6 +22,56 @@ android:background="@drawable/vpn_state_background" android:orientation="vertical" > + + + + + + + + + + + + + No CA certificate selected Please select one or activate Select automatically + + Status: + Profile: + Connecting… + Connected + Disconnecting… + No active VPN + Enter password to connect Connect + Connecting: %1$s + Establishing VPN with \""%1$s\". diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java index 5c4ffdd5d..fc250f18d 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -18,23 +18,35 @@ package org.strongswan.android.ui; import org.strongswan.android.R; +import org.strongswan.android.data.VpnProfile; import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.logic.VpnStateService.VpnStateListener; import android.app.Fragment; +import android.app.ProgressDialog; import android.app.Service; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; +import android.graphics.Color; import android.os.Bundle; import android.os.IBinder; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; public class VpnStateFragment extends Fragment implements VpnStateListener { + private TextView mProfileNameView; + private TextView mProfileView; + private TextView mStateView; + private int stateBaseColor; + private ProgressDialog mProgressDialog; + private State mState; private VpnStateService mService; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override @@ -47,6 +59,8 @@ public class VpnStateFragment extends Fragment implements VpnStateListener public void onServiceConnected(ComponentName name, IBinder service) { mService = ((VpnStateService.LocalBinder)service).getService(); + mService.registerListener(VpnStateFragment.this); + updateView(); } }; @@ -66,13 +80,30 @@ public class VpnStateFragment extends Fragment implements VpnStateListener Bundle savedInstanceState) { View view = inflater.inflate(R.layout.vpn_state_fragment, null); + + mStateView = (TextView)view.findViewById(R.id.vpn_state); + stateBaseColor = mStateView.getCurrentTextColor(); + mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label); + mProfileNameView = (TextView)view.findViewById(R.id.vpn_profile_name); + return view; } + @Override + public void onStart() + { + super.onStart(); + if (mService != null) + { + updateView(); + } + } + @Override public void onStop() { super.onStop(); + hideProgressDialog(); } @Override @@ -89,5 +120,103 @@ public class VpnStateFragment extends Fragment implements VpnStateListener @Override public void stateChanged() { + updateView(); + } + + public void updateView() + { + State state = mService.getState(); + String name = "", gateway = ""; + + if (state != State.DISABLED) + { + VpnProfile profile = mService.getProfile(); + if (profile != null) + { + name = profile.getName(); + gateway = profile.getGateway(); + } + } + + if (state == mState) + { /* avoid unnecessary updates */ + return; + } + + hideProgressDialog(); + mProfileNameView.setText(name); + mState = state; + + switch (state) + { + case DISABLED: + showProfile(false); + mStateView.setText(R.string.state_disabled); + mStateView.setTextColor(stateBaseColor); + break; + case CONNECTING: + showProfile(true); + showConnectDialog(name, gateway); + mStateView.setText(R.string.state_connecting); + mStateView.setTextColor(stateBaseColor); + break; + case CONNECTED: + showProfile(true); + mStateView.setText(R.string.state_connected); + mStateView.setTextColor(Color.GREEN); + break; + case DISCONNECTING: + showProfile(true); + showDisconnectDialog(name); + mStateView.setText(R.string.state_disconnecting); + mStateView.setTextColor(stateBaseColor); + break; + } + } + + private void showProfile(boolean show) + { + mProfileView.setVisibility(show ? View.VISIBLE : View.GONE); + mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE); + } + + private void hideProgressDialog() + { + if (mProgressDialog != null) + { + mProgressDialog.dismiss(); + mProgressDialog = null; + } + } + + private void showConnectDialog(String profile, String gateway) + { + mProgressDialog = new ProgressDialog(getActivity()); + mProgressDialog.setTitle(String.format(getString(R.string.connecting_title), profile)); + mProgressDialog.setMessage(String.format(getString(R.string.connecting_message), gateway)); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setCancelable(false); + mProgressDialog.setButton(getString(android.R.string.cancel), + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (mService != null) + { + mService.disconnect(); + } + } + }); + mProgressDialog.show(); + } + + private void showDisconnectDialog(String profile) + { + mProgressDialog = new ProgressDialog(getActivity()); + mProgressDialog.setMessage(getString(R.string.state_disconnecting)); + mProgressDialog.setIndeterminate(true); + mProgressDialog.setCancelable(false); + mProgressDialog.show(); } } From 8062f973e1481d51b69a619212b752ab95abf8fe Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:27:34 +0200 Subject: [PATCH 098/119] Show a button to disconnect the VPN once it is established --- .../android/res/layout/vpn_state_fragment.xml | 11 +++++++++ src/frontends/android/res/values/strings.xml | 1 + .../android/ui/VpnStateFragment.java | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/frontends/android/res/layout/vpn_state_fragment.xml b/src/frontends/android/res/layout/vpn_state_fragment.xml index 12d890a90..6353f3289 100644 --- a/src/frontends/android/res/layout/vpn_state_fragment.xml +++ b/src/frontends/android/res/layout/vpn_state_fragment.xml @@ -72,6 +72,17 @@ + + Status: Profile: + Disconnect Connecting… Connected Disconnecting… diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java index fc250f18d..94475a3c9 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -36,7 +36,9 @@ import android.os.Bundle; import android.os.IBinder; import android.view.LayoutInflater; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.widget.Button; import android.widget.TextView; public class VpnStateFragment extends Fragment implements VpnStateListener @@ -45,6 +47,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener private TextView mProfileView; private TextView mStateView; private int stateBaseColor; + private Button mActionButton; private ProgressDialog mProgressDialog; private State mState; private VpnStateService mService; @@ -81,6 +84,19 @@ public class VpnStateFragment extends Fragment implements VpnStateListener { View view = inflater.inflate(R.layout.vpn_state_fragment, null); + mActionButton = (Button)view.findViewById(R.id.action); + mActionButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) + { + if (mService != null) + { + mService.disconnect(); + } + } + }); + enableActionButton(false); + mStateView = (TextView)view.findViewById(R.id.vpn_state); stateBaseColor = mStateView.getCurrentTextColor(); mProfileView = (TextView)view.findViewById(R.id.vpn_profile_label); @@ -144,6 +160,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener } hideProgressDialog(); + enableActionButton(false); mProfileNameView.setText(name); mState = state; @@ -162,6 +179,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener break; case CONNECTED: showProfile(true); + enableActionButton(true); mStateView.setText(R.string.state_connected); mStateView.setTextColor(Color.GREEN); break; @@ -180,6 +198,12 @@ public class VpnStateFragment extends Fragment implements VpnStateListener mProfileNameView.setVisibility(show ? View.VISIBLE : View.GONE); } + private void enableActionButton(boolean enable) + { + mActionButton.setEnabled(enable); + mActionButton.setVisibility(enable ? View.VISIBLE : View.GONE); + } + private void hideProgressDialog() { if (mProgressDialog != null) From 264dd8d3727fed6859dab750b436542d9063e765 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:33:22 +0200 Subject: [PATCH 099/119] Show an error dialog when errors occur while establishing the VPN --- src/frontends/android/res/values/strings.xml | 7 ++ .../android/ui/VpnStateFragment.java | 74 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 4a719ef1c..04e09265e 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -58,10 +58,17 @@ Connected Disconnecting… No active VPN + Error Enter password to connect Connect + Failed to establish VPN: + Gateway address lookup failed. + Gateway is unreachable. + Verifying gateway authentication failed. + User authentication failed. + Unspecified failure while connecting. Connecting: %1$s Establishing VPN with \""%1$s\". diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java index 94475a3c9..a164ff1c6 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -20,9 +20,11 @@ package org.strongswan.android.ui; import org.strongswan.android.R; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.logic.VpnStateService; +import org.strongswan.android.logic.VpnStateService.ErrorState; import org.strongswan.android.logic.VpnStateService.State; import org.strongswan.android.logic.VpnStateService.VpnStateListener; +import android.app.AlertDialog; import android.app.Fragment; import android.app.ProgressDialog; import android.app.Service; @@ -50,6 +52,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener private Button mActionButton; private ProgressDialog mProgressDialog; private State mState; + private AlertDialog mErrorDialog; private VpnStateService mService; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override @@ -119,6 +122,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener public void onStop() { super.onStop(); + hideErrorDialog(); hideProgressDialog(); } @@ -142,6 +146,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener public void updateView() { State state = mService.getState(); + ErrorState error = ErrorState.NO_ERROR; String name = "", gateway = ""; if (state != State.DISABLED) @@ -152,6 +157,12 @@ public class VpnStateFragment extends Fragment implements VpnStateListener name = profile.getName(); gateway = profile.getGateway(); } + error = mService.getErrorState(); + } + + if (reportError(name, state, error)) + { + return; } if (state == mState) @@ -192,6 +203,44 @@ public class VpnStateFragment extends Fragment implements VpnStateListener } } + private boolean reportError(String name, State state, ErrorState error) + { + if (error == ErrorState.NO_ERROR || state != State.CONNECTING && state != State.CONNECTED) + { /* we only report errors while initiating */ + hideErrorDialog(); + return false; + } + else if (mErrorDialog != null) + { /* we already show the dialog */ + return true; + } + hideProgressDialog(); + mProfileNameView.setText(name); + showProfile(true); + enableActionButton(false); + mStateView.setText(R.string.state_error); + mStateView.setTextColor(Color.RED); + switch (error) + { + case AUTH_FAILED: + showErrorDialog(R.string.error_auth_failed); + break; + case PEER_AUTH_FAILED: + showErrorDialog(R.string.error_peer_auth_failed); + break; + case LOOKUP_FAILED: + showErrorDialog(R.string.error_lookup_failed); + break; + case UNREACHABLE: + showErrorDialog(R.string.error_unreachable); + break; + default: + showErrorDialog(R.string.error_generic); + break; + } + return true; + } + private void showProfile(boolean show) { mProfileView.setVisibility(show ? View.VISIBLE : View.GONE); @@ -213,6 +262,15 @@ public class VpnStateFragment extends Fragment implements VpnStateListener } } + private void hideErrorDialog() + { + if (mErrorDialog != null) + { + mErrorDialog.dismiss(); + mErrorDialog = null; + } + } + private void showConnectDialog(String profile, String gateway) { mProgressDialog = new ProgressDialog(getActivity()); @@ -243,4 +301,20 @@ public class VpnStateFragment extends Fragment implements VpnStateListener mProgressDialog.setCancelable(false); mProgressDialog.show(); } + + private void showErrorDialog(int textid) + { + mErrorDialog = new AlertDialog.Builder(getActivity()) + .setMessage(getString(R.string.error_introduction) + " " + getString(textid)) + .setCancelable(false) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) + { + mErrorDialog = null; + dialog.dismiss(); + } + }).create(); + mErrorDialog.show(); + } } From bebe2d397e8229757fa32bd2ec7cdf6a9006eb78 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:35:24 +0200 Subject: [PATCH 100/119] Keep reporting the error until the user dismisses it Even when the Activity is closed and later reopened. --- .../android/ui/VpnStateFragment.java | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java index a164ff1c6..ac632fd98 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -45,6 +45,9 @@ import android.widget.TextView; public class VpnStateFragment extends Fragment implements VpnStateListener { + private static final String KEY_ERROR = "error"; + private static final String KEY_NAME = "name"; + private TextView mProfileNameView; private TextView mProfileView; private TextView mStateView; @@ -53,6 +56,8 @@ public class VpnStateFragment extends Fragment implements VpnStateListener private ProgressDialog mProgressDialog; private State mState; private AlertDialog mErrorDialog; + private ErrorState mError; + private String mErrorProfileName; private VpnStateService mService; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override @@ -79,6 +84,22 @@ public class VpnStateFragment extends Fragment implements VpnStateListener Context context = getActivity().getApplicationContext(); context.bindService(new Intent(context, VpnStateService.class), mServiceConnection, Service.BIND_AUTO_CREATE); + + mError = ErrorState.NO_ERROR; + if (savedInstanceState != null && savedInstanceState.containsKey(KEY_ERROR)) + { + mError = (ErrorState)savedInstanceState.getSerializable(KEY_ERROR); + mErrorProfileName = savedInstanceState.getString(KEY_NAME); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + + outState.putSerializable(KEY_ERROR, mError); + outState.putString(KEY_NAME, mErrorProfileName); } @Override @@ -205,8 +226,22 @@ public class VpnStateFragment extends Fragment implements VpnStateListener private boolean reportError(String name, State state, ErrorState error) { - if (error == ErrorState.NO_ERROR || state != State.CONNECTING && state != State.CONNECTED) - { /* we only report errors while initiating */ + if (mError != ErrorState.NO_ERROR) + { /* we are currently reporting an error which was not yet dismissed */ + error = mError; + name = mErrorProfileName; + } + else if (error != ErrorState.NO_ERROR && (state == State.CONNECTING || state == State.CONNECTED)) + { /* while initiating we report errors */ + mError = error; + mErrorProfileName = name; + } + else + { /* ignore all other errors */ + error = ErrorState.NO_ERROR; + } + if (error == ErrorState.NO_ERROR) + { hideErrorDialog(); return false; } @@ -310,8 +345,10 @@ public class VpnStateFragment extends Fragment implements VpnStateListener .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) - { + { /* clear the error */ + mError = ErrorState.NO_ERROR; mErrorDialog = null; + updateView(); dialog.dismiss(); } }).create(); From 9c0be3ac6999b4cca137d7a3f578b822333e344b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:38:18 +0200 Subject: [PATCH 101/119] Show MainActiviy if the user clicks 'Configure' in Android's VPN dialog --- .../strongswan/android/logic/CharonVpnService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 58730c008..2938075a6 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -29,9 +29,12 @@ import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; import org.strongswan.android.logic.VpnStateService.ErrorState; import org.strongswan.android.logic.VpnStateService.State; +import org.strongswan.android.ui.MainActivity; +import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.net.VpnService; @@ -468,6 +471,14 @@ public class CharonVpnService extends VpnService implements Runnable { builder = new CharonVpnService.Builder(); builder.setSession(name); + + /* even though the option displayed in the system dialog says "Configure" + * we just use our main Activity */ + Context context = getApplicationContext(); + Intent intent = new Intent(context, MainActivity.class); + PendingIntent pending = PendingIntent.getActivity(context, 0, intent, + Intent.FLAG_ACTIVITY_NEW_TASK); + builder.setConfigureIntent(pending); } public synchronized boolean addAddress(String address, int prefixLength) From 05427857e2adffceae8960f878b176aa57a650cb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 11:53:55 +0200 Subject: [PATCH 102/119] German translation added --- .../android/res/values-de/strings.xml | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/frontends/android/res/values-de/strings.xml diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml new file mode 100644 index 000000000..7299acfaf --- /dev/null +++ b/src/frontends/android/res/values-de/strings.xml @@ -0,0 +1,75 @@ + + + + + + strongSwan VPN Client + strongSwan + CA-Zertifikate neu laden + + + Keine VPN Profile vorhanden. + Profil hinzufügen + Bearbeiten + Löschen + Ausgewählte Profile gelöscht + Kein Profil ausgewählt + Ein Profil ausgewählt + %1$d Profile ausgewählt + + + Speichern + Abbrechen + Profilname: + (Gateway-Adresse verwenden) + Gateway: + Benutzername: + Passwort: + (anfordern wenn benötigt) + CA-Zertifikat: + Automatisch wählen + Alle Zertifikate anzeigen + + Bitte geben Sie hier die Gateway-Adresse ein + Bitte geben Sie hier Ihren Benutzernamen ein + Kein CA-Zertifikat ausgewählt + Bitte wählen Sie eines aus oder aktivieren Sie Automatisch wählen + + + Status: + Profil: + Trennen + Verbinden… + Verbunden + Trennen… + Kein aktives Profil + Fehler + + + Passwort eingeben um zu verbinden + Verbinden + Fehler beim Aufsetzen des VPN: + Gateway-Adresse konnte nicht aufgelöst werden. + Gateway ist nicht erreichbar. + Authentifizierung des Gateway ist fehlgeschlagen. + Benutzerauthentifizierung ist fehlgeschlagen. + Unbekannter Fehler während des Verbindens. + Verbinden: %1$s + Verbinde mit \""%1$s\". + + From 374f62535f07572ac945d08c65de73d2b127d2da Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 12:26:48 +0200 Subject: [PATCH 103/119] Localized title for contextual action bar --- src/frontends/android/res/values-de/strings.xml | 1 + src/frontends/android/res/values/strings.xml | 1 + .../src/org/strongswan/android/ui/VpnProfileListFragment.java | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 7299acfaf..ed4456497 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -27,6 +27,7 @@ Profil hinzufügen Bearbeiten Löschen + Profile auswählen Ausgewählte Profile gelöscht Kein Profil ausgewählt Ein Profil ausgewählt diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 04e09265e..0dad64233 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -27,6 +27,7 @@ Add VPN profile Edit Delete + Select profiles Selected profiles deleted No profile selected One profile selected diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java index be0a9004e..1052558f2 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnProfileListFragment.java @@ -191,7 +191,7 @@ public class VpnProfileListFragment extends Fragment inflater.inflate(R.menu.profile_list_context, menu); mEditProfile = menu.findItem(R.id.edit_profile); mSelected = new HashSet(); - mode.setTitle("Select Profiles"); + mode.setTitle(R.string.select_profiles); return true; } From c0fe43f0029c171c05df107f7338497b17fe2e0b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 15:01:19 +0200 Subject: [PATCH 104/119] Use colors from the Android color palette for the VPN status texts --- src/frontends/android/res/values/colors.xml | 24 +++++++++++++++++++ .../android/ui/VpnStateFragment.java | 5 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 src/frontends/android/res/values/colors.xml diff --git a/src/frontends/android/res/values/colors.xml b/src/frontends/android/res/values/colors.xml new file mode 100644 index 000000000..be64d5d5a --- /dev/null +++ b/src/frontends/android/res/values/colors.xml @@ -0,0 +1,24 @@ + + + + + #D9192C + + #99CC00 + + diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java index ac632fd98..b86fd187b 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -33,7 +33,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; -import android.graphics.Color; import android.os.Bundle; import android.os.IBinder; import android.view.LayoutInflater; @@ -213,7 +212,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener showProfile(true); enableActionButton(true); mStateView.setText(R.string.state_connected); - mStateView.setTextColor(Color.GREEN); + mStateView.setTextColor(getResources().getColor(R.color.success_text)); break; case DISCONNECTING: showProfile(true); @@ -254,7 +253,7 @@ public class VpnStateFragment extends Fragment implements VpnStateListener showProfile(true); enableActionButton(false); mStateView.setText(R.string.state_error); - mStateView.setTextColor(Color.RED); + mStateView.setTextColor(getResources().getColor(R.color.error_text)); switch (error) { case AUTH_FAILED: From 496e096e7ba420fc1c7feba43157691fdf030c85 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 16:00:35 +0200 Subject: [PATCH 105/119] Load single certificates directly from the KeyStore if we cannot get the read lock This helps when running in the emulator as loading the certificates takes quite a while there. This way a configured CA certificates is loaded directly without having to wait for all certificates being cached. --- .../logic/TrustedCertificateManager.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java index 04a292a00..74868dc44 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java +++ b/src/frontends/android/src/org/strongswan/android/logic/TrustedCertificateManager.java @@ -147,9 +147,32 @@ public class TrustedCertificateManager */ public X509Certificate getCACertificateFromAlias(String alias) { - this.mLock.readLock().lock(); - X509Certificate certificate = this.mCACerts.get(alias); - this.mLock.readLock().unlock(); + X509Certificate certificate = null; + + if (this.mLock.readLock().tryLock()) + { + certificate = this.mCACerts.get(alias); + this.mLock.readLock().unlock(); + } + else + { /* if we cannot get the lock load it directly from the KeyStore, + * should be fast for a single certificate */ + try + { + KeyStore store = KeyStore.getInstance("AndroidCAStore"); + store.load(null, null); + Certificate cert = store.getCertificate(alias); + if (cert != null && cert instanceof X509Certificate) + { + certificate = (X509Certificate)cert; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + } return certificate; } From a7c8b166a1401532ebdbfd9c2e6111feb1740f74 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 16:03:14 +0200 Subject: [PATCH 106/119] Only call disconnect() from CharonVpnService if we are not already disconnecting --- .../strongswan/android/logic/CharonVpnService.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 2938075a6..069f0007d 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -53,6 +53,7 @@ public class CharonVpnService extends VpnService implements Runnable private VpnProfile mNextProfile; private volatile boolean mProfileUpdated; private volatile boolean mTerminate; + private volatile boolean mIsDisconnecting; private VpnStateService mService; private final Object mServiceLock = new Object(); private final ServiceConnection mServiceConnection = new ServiceConnection() { @@ -200,6 +201,7 @@ public class CharonVpnService extends VpnService implements Runnable setProfile(mCurrentProfile); setError(ErrorState.NO_ERROR); setState(State.CONNECTING); + mIsDisconnecting = false; BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName()); initializeCharon(builder); @@ -230,6 +232,7 @@ public class CharonVpnService extends VpnService implements Runnable if (mCurrentProfile != null) { setState(State.DISCONNECTING); + mIsDisconnecting = true; deinitializeCharon(); Log.i(TAG, "charon stopped"); mCurrentProfile = null; @@ -301,7 +304,10 @@ public class CharonVpnService extends VpnService implements Runnable if (mService != null) { mService.setError(error); - mService.disconnect(); + if (!mIsDisconnecting) + { + mService.disconnect(); + } } } } @@ -319,9 +325,9 @@ public class CharonVpnService extends VpnService implements Runnable case STATE_CHILD_SA_DOWN: synchronized (mServiceLock) { - /* since this state is also reached when the SA is closed remotely, - * we call disconnect() to make sure charon is properly deinitialized */ - if (mService != null) + /* if we are not actively disconnecting we assume the remote terminated + * the connection and call disconnect() to deinitialize charon properly */ + if (mService != null && !mIsDisconnecting) { mService.disconnect(); } From 6db742e7e5e87914a1b13eb20102478b49de4964 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 16:36:48 +0200 Subject: [PATCH 107/119] Log charon version and uname() output, split libcharon and charon initialization --- .../jni/libandroidbridge/charonservice.c | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 3a5d0cb1e..232033899 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -17,6 +17,7 @@ #include #include +#include #include #include "charonservice.h" @@ -355,6 +356,7 @@ JNI_METHOD(CharonVpnService, initializeCharon, void, jobject builder) { struct sigaction action; + struct utsname utsname; /* logging for library during initialization, as we have no bus yet */ dbg = dbg_android; @@ -381,10 +383,25 @@ JNI_METHOD(CharonVpnService, initializeCharon, void, return; } + if (!libcharon_init("charon")) + { + libcharon_deinit(); + libipsec_deinit(); + libhydra_deinit(); + library_deinit(); + return; + } + charonservice_init(env, this, builder); - if (!libcharon_init("charon") || - !charon->initialize(charon, PLUGINS)) + if (uname(&utsname) != 0) + { + memset(&utsname, 0, sizeof(utsname)); + } + DBG1(DBG_DMN, "Starting IKE charon daemon (strongSwan "VERSION", %s %s, %s)", + utsname.sysname, utsname.release, utsname.machine); + + if (!charon->initialize(charon, PLUGINS)) { libcharon_deinit(); charonservice_deinit(env); @@ -413,6 +430,7 @@ JNI_METHOD(CharonVpnService, initializeCharon, void, */ JNI_METHOD(CharonVpnService, deinitializeCharon, void) { + /* deinitialize charon before we destroy our own objects */ libcharon_deinit(); charonservice_deinit(env); libipsec_deinit(); From 4308ce1cf72c18d069606823427cd93eca386ed5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 9 Aug 2012 16:38:19 +0200 Subject: [PATCH 108/119] Moved Java to C string conversion function to android_jni header file --- .../jni/libandroidbridge/android_jni.h | 19 +++++++++++++++ .../jni/libandroidbridge/charonservice.c | 23 ++++--------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/android_jni.h b/src/frontends/android/jni/libandroidbridge/android_jni.h index 39ba56fc1..774d37d7e 100644 --- a/src/frontends/android/jni/libandroidbridge/android_jni.h +++ b/src/frontends/android/jni/libandroidbridge/android_jni.h @@ -81,4 +81,23 @@ static inline bool androidjni_exception_occurred(JNIEnv *env) return FALSE; } +/** + * Convert a Java string to a C string. Memory is allocated. + * + * @param env JNIEnv + * @param jstr Java string + * @return native C string (allocated) + */ +static inline char *androidjni_convert_jstring(JNIEnv *env, jstring jstr) +{ + char *str; + jsize len; + + len = (*env)->GetStringUTFLength(env, jstr); + str = malloc(len + 1); + (*env)->GetStringUTFRegion(env, jstr, 0, len, str); + str[len] = '\0'; + return str; +} + #endif /** ANDROID_JNI_H_ @}*/ diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 232033899..802e087ec 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -438,21 +438,6 @@ JNI_METHOD(CharonVpnService, deinitializeCharon, void) library_deinit(); } -/** - * Convert a Java string to a C string. Memory is allocated. - */ -static inline char *convert_jstring(JNIEnv *env, jstring jstr) -{ - char *str; - jsize len; - - len = (*env)->GetStringUTFLength(env, jstr); - str = malloc(len + 1); - (*env)->GetStringUTFRegion(env, jstr, 0, len, str); - str[len] = '\0'; - return str; -} - /** * Initiate SA */ @@ -462,10 +447,10 @@ JNI_METHOD(CharonVpnService, initiate, void, { char *local_address, *gateway, *username, *password; - local_address = convert_jstring(env, jlocal_address); - gateway = convert_jstring(env, jgateway); - username = convert_jstring(env, jusername); - password = convert_jstring(env, jpassword); + local_address = androidjni_convert_jstring(env, jlocal_address); + gateway = androidjni_convert_jstring(env, jgateway); + username = androidjni_convert_jstring(env, jusername); + password = androidjni_convert_jstring(env, jpassword); initiate(local_address, gateway, username, password); } From fe05f1f05c89818ac2e16c32032aa28af9b4200b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 09:06:49 +0200 Subject: [PATCH 109/119] Charon logs to a file in the App's data directory --- .../jni/libandroidbridge/charonservice.c | 37 ++++++++++++++++++- .../android/logic/CharonVpnService.java | 11 +++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/charonservice.c b/src/frontends/android/jni/libandroidbridge/charonservice.c index 802e087ec..fab99ac10 100644 --- a/src/frontends/android/jni/libandroidbridge/charonservice.c +++ b/src/frontends/android/jni/libandroidbridge/charonservice.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "charonservice.h" #include "android_jni.h" @@ -117,6 +118,35 @@ static void dbg_android(debug_t group, level_t level, char *fmt, ...) } } +/** + * Initialize file logger + */ +static void initialize_logger(char *logfile) +{ + file_logger_t *file_logger; + debug_t group; + FILE *file; + + /* truncate an existing file */ + file = fopen(logfile, "w"); + if (!file) + { + DBG1(DBG_DMN, "opening file %s for logging failed: %s", + logfile, strerror(errno)); + return; + } + /* flush each line */ + setlinebuf(file); + + file_logger = file_logger_create(file, "%b %e %T", FALSE); + for (group = 0; group < DBG_MAX; group++) + { + file_logger->set_level(file_logger, group, ANDROID_DEBUG_LEVEL); + } + charon->file_loggers->insert_last(charon->file_loggers, file_logger); + charon->bus->add_logger(charon->bus, &file_logger->logger); +} + METHOD(charonservice_t, update_status, bool, private_charonservice_t *this, android_vpn_state_t code) { @@ -353,10 +383,11 @@ static void segv_handler(int signal) * Initialize charon and the libraries via JNI */ JNI_METHOD(CharonVpnService, initializeCharon, void, - jobject builder) + jobject builder, jstring jlogfile) { struct sigaction action; struct utsname utsname; + char *logfile; /* logging for library during initialization, as we have no bus yet */ dbg = dbg_android; @@ -392,6 +423,10 @@ JNI_METHOD(CharonVpnService, initializeCharon, void, return; } + logfile = androidjni_convert_jstring(env, jlogfile); + initialize_logger(logfile); + free(logfile); + charonservice_init(env, this, builder); if (uname(&utsname) != 0) 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 069f0007d..c9c1ad02a 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -17,6 +17,7 @@ package org.strongswan.android.logic; +import java.io.File; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; @@ -46,6 +47,9 @@ import android.util.Log; public class CharonVpnService extends VpnService implements Runnable { private static final String TAG = CharonVpnService.class.getSimpleName(); + public static final String LOG_FILE = "charon.log"; + + private String mLogFile; private VpnProfileDataSource mDataSource; private Thread mConnectionHandler; private VpnProfile mCurrentProfile; @@ -113,6 +117,8 @@ public class CharonVpnService extends VpnService implements Runnable @Override public void onCreate() { + mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE; + mDataSource = new VpnProfileDataSource(this); mDataSource.open(); /* use a separate thread as main thread for charon */ @@ -204,7 +210,7 @@ public class CharonVpnService extends VpnService implements Runnable mIsDisconnecting = false; BuilderAdapter builder = new BuilderAdapter(mCurrentProfile.getName()); - initializeCharon(builder); + initializeCharon(builder, mLogFile); Log.i(TAG, "charon started"); String local_address = getLocalIPv4Address(); @@ -418,8 +424,9 @@ public class CharonVpnService extends VpnService implements Runnable * Initialization of charon, provided by libandroidbridge.so * * @param builder BuilderAdapter for this connection + * @param logfile absolute path to the logfile */ - public native void initializeCharon(BuilderAdapter builder); + public native void initializeCharon(BuilderAdapter builder, String logfile); /** * Deinitialize charon, provided by libandroidbridge.so From 658ed96fce97d083c8e027f3ce854729da79a641 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 09:37:20 +0200 Subject: [PATCH 110/119] Added special ScrollView with auto-scrolling feature The ability to auto-scroll is disabled as soon as the user manually scrolls around and re-enable when the user scrolls to the bottom. --- .../strongswan/android/ui/LogScrollView.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java b/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java new file mode 100644 index 000000000..7eee820ce --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/LogScrollView.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012 Giuliano Grassi + * Copyright (C) 2012 Ralf Sager + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ScrollView; + +public class LogScrollView extends ScrollView +{ + private boolean mAutoScroll = true; + + public LogScrollView(Context context) + { + super(context); + } + + public LogScrollView(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public LogScrollView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) + { + /* disable auto-scrolling when the user starts scrolling around */ + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) + { + mAutoScroll = false; + } + return super.onTouchEvent(ev); + } + + /** + * Call this to move newly added content into view by scrolling to the bottom. + * Nothing happens if auto-scrolling is disabled. + */ + public void autoScroll() + { + if (mAutoScroll) + { + fullScroll(View.FOCUS_DOWN); + } + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) + { + super.onScrollChanged(l, t, oldl, oldt); + /* if the user scrolls to the bottom we enable auto-scrolling again */ + if (t == getChildAt(getChildCount() - 1).getHeight() - getHeight()) + { + mAutoScroll = true; + } + } +} From f9a162a235b17242171ea60d6b3607b603508902 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 09:50:52 +0200 Subject: [PATCH 111/119] Add a fragment that can display charon's log file It continuously reads from the log file in a separate thread while displayed. --- .../android/res/layout/log_fragment.xml | 41 +++++ .../strongswan/android/ui/LogFragment.java | 146 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 src/frontends/android/res/layout/log_fragment.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/LogFragment.java diff --git a/src/frontends/android/res/layout/log_fragment.xml b/src/frontends/android/res/layout/log_fragment.xml new file mode 100644 index 000000000..c2e187a66 --- /dev/null +++ b/src/frontends/android/res/layout/log_fragment.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java new file mode 100644 index 000000000..23cd2aa46 --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.StringReader; + +import org.strongswan.android.R; +import org.strongswan.android.logic.CharonVpnService; + +import android.app.Fragment; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class LogFragment extends Fragment implements Runnable +{ + private String mLogFilePath; + private Handler mLogHandler; + private TextView mLogView; + private LogScrollView mScrollView; + private BufferedReader mReader; + private Thread mThread; + private volatile boolean mRunning; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; + /* use a handler to update the log view */ + mLogHandler = new Handler(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + View view = inflater.inflate(R.layout.log_fragment, null); + mLogView = (TextView)view.findViewById(R.id.log_view); + mScrollView = (LogScrollView)view.findViewById(R.id.scroll_view); + return view; + } + + @Override + public void onStart() + { + super.onStart(); + mLogView.setText(""); + try + { + mReader = new BufferedReader(new FileReader(mLogFilePath)); + } + catch (FileNotFoundException e) + { + mReader = new BufferedReader(new StringReader("")); + } + mRunning = true; + mThread = new Thread(this); + mThread.start(); + } + + @Override + public void onStop() + { + super.onStop(); + try + { + mRunning = false; + mThread.interrupt(); + mThread.join(); + } + catch (InterruptedException e) + { + } + } + + /** + * Write the given log line to the TextView. We strip the prefix off to save + * some space (it is not that helpful for regular users anyway). + * @param line log line to log + */ + public void logLine(final String line) + { + mLogHandler.post(new Runnable() { + @Override + public void run() + { + /* strip off prefix (month=3, day=2, time=8, thread=2, spaces=3) */ + mLogView.append((line.length() > 18 ? line.substring(18) : line) + '\n'); + /* calling autoScroll() directly does not work, probably because content + * is not yet updated, so we post this to be done later */ + mScrollView.post(new Runnable() { + @Override + public void run() + { + mScrollView.autoScroll(); + } + }); + } + }); + } + + @Override + public void run() + { + while (mRunning) + { + try + { + String line = mReader.readLine(); + if (line == null) + { /* wait until there is more to log */ + Thread.sleep(1000); + } + else + { + logLine(line); + } + } + catch (Exception e) + { + break; + } + } + } +} From bad119c55a9e64c4fff96a009f89645fd94c59e7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 09:58:56 +0200 Subject: [PATCH 112/119] Add an Activity that shows the log fragment --- src/frontends/android/AndroidManifest.xml | 21 +++++++++ .../android/res/layout/log_activity.xml | 26 +++++++++++ src/frontends/android/res/menu/main.xml | 5 ++ .../android/res/values-de/strings.xml | 4 ++ src/frontends/android/res/values/strings.xml | 4 ++ .../strongswan/android/ui/LogActivity.java | 46 +++++++++++++++++++ .../strongswan/android/ui/MainActivity.java | 8 ++-- 7 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/frontends/android/res/layout/log_activity.xml create mode 100644 src/frontends/android/src/org/strongswan/android/ui/LogActivity.java diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 5c8686e79..62fc6a135 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -1,4 +1,20 @@ + + + + + + + + + + diff --git a/src/frontends/android/res/menu/main.xml b/src/frontends/android/res/menu/main.xml index f5d1e31cf..4063110da 100644 --- a/src/frontends/android/res/menu/main.xml +++ b/src/frontends/android/res/menu/main.xml @@ -20,4 +20,9 @@ android:title="@string/reload_trusted_certs" android:showAsAction="withText" /> + + \ No newline at end of file diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index ed4456497..7f7288d5f 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -21,6 +21,10 @@ strongSwan VPN Client strongSwan CA-Zertifikate neu laden + Log anzeigen + + + Log Keine VPN Profile vorhanden. diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 0dad64233..762273fc8 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -21,6 +21,10 @@ strongSwan VPN Client strongSwan Reload CA certificates + View log + + + Log No VPN profiles. diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java new file mode 100644 index 000000000..84bb458af --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.ui; + +import org.strongswan.android.R; + +import android.app.Activity; +import android.os.Bundle; +import android.view.MenuItem; + +public class LogActivity extends Activity +{ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.log_activity); + + getActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + case android.R.id.home: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index 50b2bfbe5..f9d6c1725 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -38,7 +38,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.Window; @@ -66,8 +65,7 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main, menu); + getMenuInflater().inflate(R.menu.main, menu); return true; } @@ -79,6 +77,10 @@ public class MainActivity extends Activity implements OnVpnProfileSelectedListen case R.id.menu_reload_certs: new CertificateLoadTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true); return true; + case R.id.menu_show_log: + Intent logIntent = new Intent(this, LogActivity.class); + startActivity(logIntent); + return true; default: return super.onOptionsItemSelected(item); } From ae10e8c458cd87b5bbcf1e40e1ce430335c9bb65 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 10:05:38 +0200 Subject: [PATCH 113/119] Watch for changes to the log file so we can reopen it If the log fragment is shown while the daemon starts (which is not the case at the moment, but maybe later on tablets) the file reader would not notice that the file got truncated. The same applies if the file is deleted directly on the file system e.g. with adb shell. --- .../strongswan/android/ui/LogFragment.java | 91 ++++++++++++++++++- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java index 23cd2aa46..8740e0c46 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/LogFragment.java @@ -26,6 +26,7 @@ import org.strongswan.android.logic.CharonVpnService; import android.app.Fragment; import android.os.Bundle; +import android.os.FileObserver; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; @@ -41,6 +42,7 @@ public class LogFragment extends Fragment implements Runnable private BufferedReader mReader; private Thread mThread; private volatile boolean mRunning; + private FileObserver mDirectoryObserver; @Override public void onCreate(Bundle savedInstanceState) @@ -50,6 +52,8 @@ public class LogFragment extends Fragment implements Runnable mLogFilePath = getActivity().getFilesDir() + File.separator + CharonVpnService.LOG_FILE; /* use a handler to update the log view */ mLogHandler = new Handler(); + + mDirectoryObserver = new LogDirectoryObserver(getActivity().getFilesDir().getAbsolutePath()); } @Override @@ -65,7 +69,23 @@ public class LogFragment extends Fragment implements Runnable public void onStart() { super.onStart(); - mLogView.setText(""); + startLogReader(); + mDirectoryObserver.startWatching(); + } + + @Override + public void onStop() + { + super.onStop(); + mDirectoryObserver.stopWatching(); + stopLogReader(); + } + + /** + * Start reading from the log file + */ + private void startLogReader() + { try { mReader = new BufferedReader(new FileReader(mLogFilePath)); @@ -74,15 +94,18 @@ public class LogFragment extends Fragment implements Runnable { mReader = new BufferedReader(new StringReader("")); } + + mLogView.setText(""); mRunning = true; mThread = new Thread(this); mThread.start(); } - @Override - public void onStop() + /** + * Stop reading from the log file + */ + private void stopLogReader() { - super.onStop(); try { mRunning = false; @@ -97,6 +120,7 @@ public class LogFragment extends Fragment implements Runnable /** * Write the given log line to the TextView. We strip the prefix off to save * some space (it is not that helpful for regular users anyway). + * * @param line log line to log */ public void logLine(final String line) @@ -126,7 +150,7 @@ public class LogFragment extends Fragment implements Runnable while (mRunning) { try - { + { /* this works as long as the file is not truncated */ String line = mReader.readLine(); if (line == null) { /* wait until there is more to log */ @@ -143,4 +167,61 @@ public class LogFragment extends Fragment implements Runnable } } } + + /** + * FileObserver that checks for changes regarding the log file. Since charon + * truncates it (for which there is no explicit event) we check for any modification + * to the file, keep track of the file size and reopen it if it got smaller. + */ + private class LogDirectoryObserver extends FileObserver + { + private final File mFile; + private long mSize; + + public LogDirectoryObserver(String path) + { + super(path, FileObserver.CREATE | FileObserver.MODIFY | FileObserver.DELETE); + mFile = new File(mLogFilePath); + mSize = mFile.length(); + } + + @Override + public void onEvent(int event, String path) + { + if (path == null || !path.equals(CharonVpnService.LOG_FILE)) + { + return; + } + switch (event) + { /* even though we only subscribed for these we check them, + * as strange events are sometimes received */ + case FileObserver.CREATE: + case FileObserver.DELETE: + restartLogReader(); + break; + case FileObserver.MODIFY: + /* if the size got smaller reopen the log file, as it was probably truncated */ + long size = mFile.length(); + if (size < mSize) + { + restartLogReader(); + } + mSize = size; + break; + } + } + + private void restartLogReader() + { + /* we are called from a separate thread, so we use the handler */ + mLogHandler.post(new Runnable() { + @Override + public void run() + { + stopLogReader(); + startLogReader(); + } + }); + } + } } From c3afe9d35bca41a71aee40b2e4ab26c41bd7d965 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 16:33:05 +0200 Subject: [PATCH 114/119] Add ContentProvider to access log file from other applications --- src/frontends/android/AndroidManifest.xml | 5 + .../android/data/LogContentProvider.java | 117 ++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 62fc6a135..1b1aabac4 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -58,6 +58,11 @@ + + + diff --git a/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java new file mode 100644 index 000000000..7225ca4ef --- /dev/null +++ b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.data; + +import java.io.File; +import java.io.FileNotFoundException; + +import org.strongswan.android.logic.CharonVpnService; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.OpenableColumns; + +public class LogContentProvider extends ContentProvider +{ + private static final String AUTHORITY = "org.strongswan.android.content.log"; + private File mLogFile; + + public LogContentProvider() + { + } + + @Override + public boolean onCreate() + { + mLogFile = new File(getContext().getFilesDir(), CharonVpnService.LOG_FILE); + return true; + } + + /** + * The log file can only be accessed by Uris created with this method + * @return null if failed to create the Uri + */ + public static Uri createContentUri() + { + Uri uri = Uri.parse("content://"+ AUTHORITY + "/" + CharonVpnService.LOG_FILE); + return uri; + } + + @Override + public String getType(Uri uri) + { + /* MIME type for our log file */ + return "text/plain"; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) + { + /* this is called by apps to find out the name and size of the file. + * since we only provide a single file this is simple to implement */ + if (projection == null || projection.length < 1) + { + return null; + } + MatrixCursor cursor = new MatrixCursor(projection, 1); + if (OpenableColumns.DISPLAY_NAME.equals(cursor.getColumnName(0))) + { + cursor.newRow().add(CharonVpnService.LOG_FILE); + } + else if (OpenableColumns.SIZE.equals(cursor.getColumnName(0))) + { + cursor.newRow().add(mLogFile.length()); + } + else + { + return null; + } + return cursor; + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException + { + return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY); + } + + @Override + public Uri insert(Uri uri, ContentValues values) + { + /* not supported */ + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) + { + /* not supported */ + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) + { + /* not supported */ + return 0; + } +} From 6c54c1083840ecf882dc8cb4e72c989ed3b6d43f Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 16:37:39 +0200 Subject: [PATCH 115/119] Menu option added that allows users to send the log file --- src/frontends/android/res/menu/log.xml | 23 +++++++++++ .../android/res/values-de/strings.xml | 3 ++ src/frontends/android/res/values/strings.xml | 3 ++ .../strongswan/android/ui/LogActivity.java | 40 +++++++++++++++++++ .../strongswan/android/ui/MainActivity.java | 1 + 5 files changed, 70 insertions(+) create mode 100644 src/frontends/android/res/menu/log.xml diff --git a/src/frontends/android/res/menu/log.xml b/src/frontends/android/res/menu/log.xml new file mode 100644 index 000000000..1af5bd397 --- /dev/null +++ b/src/frontends/android/res/menu/log.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/src/frontends/android/res/values-de/strings.xml b/src/frontends/android/res/values-de/strings.xml index 7f7288d5f..0e0ddd583 100644 --- a/src/frontends/android/res/values-de/strings.xml +++ b/src/frontends/android/res/values-de/strings.xml @@ -25,6 +25,9 @@ Log + Logdatei senden + Logdatei ist leer + strongSwan %1$s Logdatei Keine VPN Profile vorhanden. diff --git a/src/frontends/android/res/values/strings.xml b/src/frontends/android/res/values/strings.xml index 762273fc8..a83e219a7 100644 --- a/src/frontends/android/res/values/strings.xml +++ b/src/frontends/android/res/values/strings.xml @@ -25,6 +25,9 @@ Log + Send log file + Log file is empty + strongSwan %1$s Log File No VPN profiles. diff --git a/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java index 84bb458af..a5efecc09 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/LogActivity.java @@ -15,11 +15,19 @@ package org.strongswan.android.ui; +import java.io.File; + import org.strongswan.android.R; +import org.strongswan.android.data.LogContentProvider; +import org.strongswan.android.logic.CharonVpnService; import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; +import android.view.Menu; import android.view.MenuItem; +import android.widget.Toast; public class LogActivity extends Activity { @@ -32,6 +40,13 @@ public class LogActivity extends Activity getActionBar().setDisplayHomeAsUpEnabled(true); } + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + getMenuInflater().inflate(R.menu.log, menu); + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -40,6 +55,31 @@ public class LogActivity extends Activity case android.R.id.home: finish(); return true; + case R.id.menu_send_log: + File logfile = new File(getFilesDir(), CharonVpnService.LOG_FILE); + if (!logfile.exists() || logfile.length() == 0) + { + Toast.makeText(this, getString(R.string.empty_log), Toast.LENGTH_SHORT).show(); + return true; + } + + String version = ""; + try + { + version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + } + catch (NameNotFoundException e) + { + e.printStackTrace(); + } + + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_EMAIL, new String[] { MainActivity.CONTACT_EMAIL }); + intent.putExtra(Intent.EXTRA_SUBJECT, String.format(getString(R.string.log_mail_subject), version)); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_STREAM, LogContentProvider.createContentUri()); + startActivity(Intent.createChooser(intent, getString(R.string.send_log))); + return true; } return super.onOptionsItemSelected(item); } diff --git a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java index f9d6c1725..80f1a27b3 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java +++ b/src/frontends/android/src/org/strongswan/android/ui/MainActivity.java @@ -45,6 +45,7 @@ import android.widget.EditText; public class MainActivity extends Activity implements OnVpnProfileSelectedListener { + public static final String CONTACT_EMAIL = "android@strongswan.org"; private static final int PREPARE_VPN_SERVICE = 0; private VpnProfile activeProfile; From 064f4f75c0b1ad997e07d428572c8ea7a1fb65eb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 16:42:49 +0200 Subject: [PATCH 116/119] Only allow access to log file via explicitly created URIs Since ContentProviders are public and permissions don't seem to work any other application could access the log file. With this token system only URIs we explicitly created can be accessed. --- src/frontends/android/AndroidManifest.xml | 3 ++ .../android/data/LogContentProvider.java | 36 +++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 1b1aabac4..1aa2d8502 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -62,6 +62,9 @@ + diff --git a/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java index 7225ca4ef..370a8d5e4 100644 --- a/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java +++ b/src/frontends/android/src/org/strongswan/android/data/LogContentProvider.java @@ -17,6 +17,9 @@ package org.strongswan.android.data; import java.io.File; import java.io.FileNotFoundException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.ConcurrentHashMap; import org.strongswan.android.logic.CharonVpnService; @@ -26,11 +29,15 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.ParcelFileDescriptor; +import android.os.SystemClock; import android.provider.OpenableColumns; public class LogContentProvider extends ContentProvider { private static final String AUTHORITY = "org.strongswan.android.content.log"; + /* an Uri is valid for 30 minutes */ + private static final long URI_VALIDITY = 30 * 60 * 1000; + private static ConcurrentHashMap mUris = new ConcurrentHashMap(); private File mLogFile; public LogContentProvider() @@ -50,7 +57,17 @@ public class LogContentProvider extends ContentProvider */ public static Uri createContentUri() { - Uri uri = Uri.parse("content://"+ AUTHORITY + "/" + CharonVpnService.LOG_FILE); + SecureRandom random; + try + { + random = SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + return null; + } + Uri uri = Uri.parse("content://" + AUTHORITY + "/" + random.nextLong()); + mUris.put(uri, SystemClock.uptimeMillis()); return uri; } @@ -71,6 +88,11 @@ public class LogContentProvider extends ContentProvider { return null; } + Long timestamp = mUris.get(uri); + if (timestamp == null) + { /* don't check the validity as this information is not really private */ + return null; + } MatrixCursor cursor = new MatrixCursor(projection, 1); if (OpenableColumns.DISPLAY_NAME.equals(cursor.getColumnName(0))) { @@ -90,7 +112,17 @@ public class LogContentProvider extends ContentProvider @Override public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY); + Long timestamp = mUris.get(uri); + if (timestamp != null) + { + long elapsed = SystemClock.uptimeMillis() - timestamp; + if (elapsed > 0 && elapsed < URI_VALIDITY) + { /* we fail if clock wrapped, should happen rarely though */ + return ParcelFileDescriptor.open(mLogFile, ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_ONLY); + } + mUris.remove(uri); + } + return super.openFile(uri, mode); } @Override From fbacc6506c0ab116cb562f5c2017b8f109b98f7a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 10 Aug 2012 16:46:09 +0200 Subject: [PATCH 117/119] Use major.minor.revision version numbers for Android application --- src/frontends/android/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontends/android/AndroidManifest.xml b/src/frontends/android/AndroidManifest.xml index 1aa2d8502..747fe1df3 100644 --- a/src/frontends/android/AndroidManifest.xml +++ b/src/frontends/android/AndroidManifest.xml @@ -18,7 +18,7 @@ + android:versionName="1.0.0" > From 1fcaa71291e896a653826fefd8e0d73a9d4ed41d Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 11 Aug 2012 16:16:45 +0200 Subject: [PATCH 118/119] Added a button to the error dialog that allows to view the log file --- .../android/ui/VpnStateFragment.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java index b86fd187b..738ed111f 100644 --- a/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java +++ b/src/frontends/android/src/org/strongswan/android/ui/VpnStateFragment.java @@ -341,16 +341,31 @@ public class VpnStateFragment extends Fragment implements VpnStateListener mErrorDialog = new AlertDialog.Builder(getActivity()) .setMessage(getString(R.string.error_introduction) + " " + getString(textid)) .setCancelable(false) + .setNeutralButton(R.string.show_log, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + Intent logIntent = new Intent(getActivity(), LogActivity.class); + startActivity(logIntent); + } + }) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) - { /* clear the error */ - mError = ErrorState.NO_ERROR; - mErrorDialog = null; - updateView(); + { dialog.dismiss(); } }).create(); + mErrorDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) + { /* clear the error */ + mError = ErrorState.NO_ERROR; + mErrorDialog = null; + updateView(); + } + }); mErrorDialog.show(); } } From efbb5e8c57330ec730825aa31ac5153f1d1c5913 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Sat, 11 Aug 2012 17:30:39 +0200 Subject: [PATCH 119/119] Ensure thread IDs always start with 1 even if the library is reused Within the Android App the library stays loaded in memory and is just initialized/deinitialized with each connection, the static thread counter would continuously increase without this patch. --- src/libstrongswan/threading/thread.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstrongswan/threading/thread.c b/src/libstrongswan/threading/thread.c index 49a1b8430..9ef514ebc 100644 --- a/src/libstrongswan/threading/thread.c +++ b/src/libstrongswan/threading/thread.c @@ -114,7 +114,7 @@ typedef struct { /** * Next thread ID. */ -static u_int next_id = 1; +static u_int next_id; /** * Mutex to safely access the next thread ID. @@ -452,6 +452,7 @@ void threads_init() dummy1 = thread_value_create(NULL); + next_id = 1; main_thread->id = 0; main_thread->thread_id = pthread_self(); current_thread = thread_value_create(NULL); @@ -482,4 +483,3 @@ void threads_deinit() current_thread->destroy(current_thread); id_mutex->destroy(id_mutex); } -