From a10eb935667fb7519e57c7a869aa76fd8549f79a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 14 Jul 2014 17:33:17 +0200 Subject: [PATCH 01/15] ip_packet: Parse ports from TCP and UDP headers --- src/libipsec/ip_packet.c | 70 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index 181cb88db9..3c2069ae7e 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2014 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -22,6 +22,8 @@ #include #include #include +#include +#include #ifdef HAVE_NETINET_IP6_H #include #endif @@ -110,6 +112,48 @@ METHOD(ip_packet_t, destroy, void, free(this); } +/** + * Parse transport protocol header + */ +static bool parse_transport_header(chunk_t packet, u_int8_t proto, + u_int16_t *sport, u_int16_t *dport) +{ + switch (proto) + { + case IPPROTO_UDP: + { + struct udphdr *udp; + + if (packet.len < sizeof(*udp)) + { + DBG1(DBG_ESP, "UDP packet too short"); + return FALSE; + } + udp = (struct udphdr*)packet.ptr; + *sport = ntohs(udp->source); + *dport = ntohs(udp->dest); + break; + } + case IPPROTO_TCP: + { + struct tcphdr *tcp; + + if (packet.len < sizeof(*tcp)) + { + DBG1(DBG_ESP, "TCP packet too short"); + return FALSE; + } + tcp = (struct tcphdr*)packet.ptr; + *sport = ntohs(tcp->source); + *dport = ntohs(tcp->dest); + break; + } + default: + break; + } + return TRUE; +} + /** * Described in header. */ @@ -117,6 +161,7 @@ ip_packet_t *ip_packet_create(chunk_t packet) { private_ip_packet_t *this; u_int8_t version, next_header; + u_int16_t sport = 0, dport = 0; host_t *src, *dst; if (packet.len < 1) @@ -142,10 +187,15 @@ ip_packet_t *ip_packet_create(chunk_t packet) /* remove any RFC 4303 TFC extra padding */ packet.len = min(packet.len, untoh16(&ip->ip_len)); + if (!parse_transport_header(chunk_skip(packet, ip->ip_hl * 4), + ip->ip_p, &sport, &dport)) + { + goto failed; + } src = host_create_from_chunk(AF_INET, - chunk_from_thing(ip->ip_src), 0); + chunk_from_thing(ip->ip_src), sport); dst = host_create_from_chunk(AF_INET, - chunk_from_thing(ip->ip_dst), 0); + chunk_from_thing(ip->ip_dst), dport); next_header = ip->ip_p; break; } @@ -154,7 +204,7 @@ ip_packet_t *ip_packet_create(chunk_t packet) { struct ip6_hdr *ip; - if (packet.len < sizeof(struct ip6_hdr)) + if (packet.len < sizeof(*ip)) { DBG1(DBG_ESP, "IPv6 packet too short"); goto failed; @@ -162,11 +212,17 @@ ip_packet_t *ip_packet_create(chunk_t packet) ip = (struct ip6_hdr*)packet.ptr; /* remove any RFC 4303 TFC extra padding */ packet.len = min(packet.len, untoh16(&ip->ip6_plen)); - + /* we only handle packets without extension headers, just skip the + * basic IPv6 header */ + if (!parse_transport_header(chunk_skip(packet, 40), ip->ip6_nxt, + &sport, &dport)) + { + goto failed; + } src = host_create_from_chunk(AF_INET6, - chunk_from_thing(ip->ip6_src), 0); + chunk_from_thing(ip->ip6_src), sport); dst = host_create_from_chunk(AF_INET6, - chunk_from_thing(ip->ip6_dst), 0); + chunk_from_thing(ip->ip6_dst), dport); next_header = ip->ip6_nxt; break; } From b557f4a7cdc6b89e11bf57dff7410e5fafa64317 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 15 Jul 2014 13:14:46 +0200 Subject: [PATCH 02/15] chunk: Add function to calculate Internet Checksums according to RFC 1071 --- src/libstrongswan/tests/suites/test_chunk.c | 49 +++++++++++++++++++++ src/libstrongswan/utils/chunk.c | 31 +++++++++++++ src/libstrongswan/utils/chunk.h | 25 +++++++++++ 3 files changed, 105 insertions(+) diff --git a/src/libstrongswan/tests/suites/test_chunk.c b/src/libstrongswan/tests/suites/test_chunk.c index b33d70ec74..d71e010a2d 100644 --- a/src/libstrongswan/tests/suites/test_chunk.c +++ b/src/libstrongswan/tests/suites/test_chunk.c @@ -783,6 +783,51 @@ START_TEST(test_chunk_hash_static) } END_TEST +/******************************************************************************* + * test for chunk_internet_checksum[_inc]() + */ + +START_TEST(test_chunk_internet_checksum) +{ + chunk_t chunk; + u_int16_t sum; + + chunk = chunk_from_chars(0x45,0x00,0x00,0x30,0x44,0x22,0x40,0x00,0x80,0x06, + 0x00,0x00,0x8c,0x7c,0x19,0xac,0xae,0x24,0x1e,0x2b); + + sum = chunk_internet_checksum(chunk); + ck_assert_int_eq(0x442e, ntohs(sum)); + + sum = chunk_internet_checksum(chunk_create(chunk.ptr, 10)); + sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+10, 10), sum); + ck_assert_int_eq(0x442e, ntohs(sum)); + + /* need to compensate for even/odd alignment */ + sum = chunk_internet_checksum(chunk_create(chunk.ptr, 9)); + sum = ntohs(sum); + sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+9, 11), sum); + sum = ntohs(sum); + ck_assert_int_eq(0x442e, ntohs(sum)); + + chunk = chunk_from_chars(0x45,0x00,0x00,0x30,0x44,0x22,0x40,0x00,0x80,0x06, + 0x00,0x00,0x8c,0x7c,0x19,0xac,0xae,0x24,0x1e); + + sum = chunk_internet_checksum(chunk); + ck_assert_int_eq(0x4459, ntohs(sum)); + + sum = chunk_internet_checksum(chunk_create(chunk.ptr, 10)); + sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+10, 9), sum); + ck_assert_int_eq(0x4459, ntohs(sum)); + + /* need to compensate for even/odd alignment */ + sum = chunk_internet_checksum(chunk_create(chunk.ptr, 9)); + sum = ntohs(sum); + sum = chunk_internet_checksum_inc(chunk_create(chunk.ptr+9, 10), sum); + sum = ntohs(sum); + ck_assert_int_eq(0x4459, ntohs(sum)); +} +END_TEST + /******************************************************************************* * test for chunk_map and friends */ @@ -1018,6 +1063,10 @@ Suite *chunk_suite_create() tcase_add_test(tc, test_chunk_hash_static); suite_add_tcase(s, tc); + tc = tcase_create("chunk_internet_checksum"); + tcase_add_test(tc, test_chunk_internet_checksum); + suite_add_tcase(s, tc); + tc = tcase_create("chunk_map"); tcase_add_test(tc, test_chunk_map); suite_add_tcase(s, tc); diff --git a/src/libstrongswan/utils/chunk.c b/src/libstrongswan/utils/chunk.c index 1a9674f4db..4b24b37c25 100644 --- a/src/libstrongswan/utils/chunk.c +++ b/src/libstrongswan/utils/chunk.c @@ -987,6 +987,37 @@ u_int32_t chunk_hash_static(chunk_t chunk) return chunk_mac(chunk, static_key); } +/** + * Described in header. + */ +u_int16_t chunk_internet_checksum_inc(chunk_t data, u_int16_t checksum) +{ + u_int32_t sum = ntohs(~checksum); + + while (data.len > 1) + { + sum += untoh16(data.ptr); + data = chunk_skip(data, 2); + } + if (data.len) + { + sum += (u_int16_t)*data.ptr << 8; + } + while (sum >> 16) + { + sum = (sum & 0xffff) + (sum >> 16); + } + return htons(~sum); +} + +/** + * Described in header. + */ +u_int16_t chunk_internet_checksum(chunk_t data) +{ + return chunk_internet_checksum_inc(data, 0xffff); +} + /** * Described in header. */ diff --git a/src/libstrongswan/utils/chunk.h b/src/libstrongswan/utils/chunk.h index 9951ff31f7..0daa2e1d0d 100644 --- a/src/libstrongswan/utils/chunk.h +++ b/src/libstrongswan/utils/chunk.h @@ -411,6 +411,31 @@ u_int32_t chunk_hash_static_inc(chunk_t chunk, u_int32_t hash); */ u_int64_t chunk_mac(chunk_t chunk, u_char *key); +/** + * Calculate the Internet Checksum according to RFC 1071 for the given chunk. + * + * If the result is used with chunk_internet_checksum_inc() and the data length + * is not a multiple of 16 bit the checksum bytes have to be swapped to + * compensate the even/odd alignment. + * + * @param chunk data to process + * @return checksum (one's complement, network order) + */ +u_int16_t chunk_internet_checksum(chunk_t data); + +/** + * Extend the given Internet Checksum (one's complement, in network byte order) + * with the given data. + * + * If data is not a multiple of 16 bits the checksum may have to be swapped to + * compensate even/odd alignment (see chunk_internet_checksum()). + * + * @param chunk data to process + * @param checksum previous checksum (one's complement, network order) + * @return checksum (one's complement, network order) + */ +u_int16_t chunk_internet_checksum_inc(chunk_t data, u_int16_t checksum); + /** * printf hook function for chunk_t. * From d56d9a45d4ea85debe2a97e5e48c4788f119f225 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 15 Jul 2014 18:02:06 +0200 Subject: [PATCH 03/15] ip_packet: Allow creation of IP packets from data --- src/libipsec/ip_packet.c | 151 +++++++++++++++++++++++++++++++++++++++ src/libipsec/ip_packet.h | 14 +++- 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index 3c2069ae7e..a846717efa 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -254,3 +254,154 @@ failed: chunk_free(&packet); return NULL; } + +/** + * Calculate the checksum for the pseudo IP header + */ +static u_int16_t pseudo_header_checksum(host_t *src, host_t *dst, + u_int8_t proto, chunk_t payload) +{ + switch (src->get_family(src)) + { + case AF_INET: + { + struct __attribute__((packed)) { + u_int32_t src; + u_int32_t dst; + u_char zero; + u_char proto; + u_int16_t len; + } pseudo = { + .proto = proto, + .len = htons(payload.len), + }; + memcpy(&pseudo.src, src->get_address(src).ptr, + sizeof(pseudo.src)); + memcpy(&pseudo.dst, dst->get_address(dst).ptr, + sizeof(pseudo.dst)); + return chunk_internet_checksum(chunk_from_thing(pseudo)); + } + case AF_INET6: + { + struct __attribute__((packed)) { + u_char src[16]; + u_char dst[16]; + u_int32_t len; + u_char zero[3]; + u_char next_header; + } pseudo = { + .next_header = proto, + .len = htons(payload.len), + }; + memcpy(&pseudo.src, src->get_address(src).ptr, + sizeof(pseudo.src)); + memcpy(&pseudo.dst, dst->get_address(dst).ptr, + sizeof(pseudo.dst)); + return chunk_internet_checksum(chunk_from_thing(pseudo)); + } + } + return 0xffff; +} + +/** + * Calculate transport header checksums + */ +static void fix_transport_checksum(host_t *src, host_t *dst, u_int8_t proto, + chunk_t payload) +{ + u_int16_t sum = 0; + + switch (proto) + { + case IPPROTO_UDP: + { + struct udphdr *udp; + + if (payload.len < sizeof(*udp)) + { + return; + } + udp = (struct udphdr*)payload.ptr; + udp->check = 0; + sum = pseudo_header_checksum(src, dst, proto, payload); + udp->check = chunk_internet_checksum_inc(payload, sum); + break; + } + case IPPROTO_TCP: + { + struct tcphdr *tcp; + + if (payload.len < sizeof(*tcp)) + { + return; + } + tcp = (struct tcphdr*)payload.ptr; + tcp->check = 0; + sum = pseudo_header_checksum(src, dst, proto, payload); + tcp->check = chunk_internet_checksum_inc(payload, sum); + break; + } + default: + break; + } +} + +/** + * Described in header. + */ +ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, + u_int8_t next_header, chunk_t data) +{ + chunk_t packet; + int family; + + family = src->get_family(src); + if (family != dst->get_family(dst)) + { + DBG1(DBG_ESP, "address family does not match"); + return NULL; + } + + switch (family) + { + case AF_INET: + { + struct ip ip = { + .ip_v = 4, + .ip_hl = 5, + .ip_len = htons(20 + data.len), + .ip_ttl = 0x80, + .ip_p = next_header, + }; + memcpy(&ip.ip_src, src->get_address(src).ptr, sizeof(ip.ip_src)); + memcpy(&ip.ip_dst, dst->get_address(dst).ptr, sizeof(ip.ip_dst)); + ip.ip_sum = chunk_internet_checksum(chunk_from_thing(ip)); + + packet = chunk_cat("cc", chunk_from_thing(ip), data); + fix_transport_checksum(src, dst, next_header, + chunk_skip(packet, 20)); + return ip_packet_create(packet); + } +#ifdef HAVE_NETINET_IP6_H + case AF_INET6: + { + struct ip6_hdr ip = { + .ip6_flow = htonl(6), + .ip6_plen = htons(40 + data.len), + .ip6_nxt = next_header, + .ip6_hlim = 0x80, + }; + memcpy(&ip.ip6_src, src->get_address(src).ptr, sizeof(ip.ip6_src)); + memcpy(&ip.ip6_dst, dst->get_address(dst).ptr, sizeof(ip.ip6_dst)); + + packet = chunk_cat("cc", chunk_from_thing(ip), data); + fix_transport_checksum(src, dst, next_header, + chunk_skip(packet, 40)); + return ip_packet_create(packet); + } +#endif /* HAVE_NETINET_IP6_H */ + default: + DBG1(DBG_ESP, "unsupported address family"); + return NULL; + } +} diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h index de817e23ed..0f44c89517 100644 --- a/src/libipsec/ip_packet.h +++ b/src/libipsec/ip_packet.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2014 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -93,4 +93,16 @@ struct ip_packet_t { */ ip_packet_t *ip_packet_create(chunk_t packet); +/** + * Encode an IP packet from the given data. + * + * @param src source address (cloned) + * @param dst destination address (cloned) + * @param next_header the protocol (IPv4) or next header (IPv6) + * @param data complete data after basic IP header (cloned) + * @return ip_packet_t instance, or NULL if invalid + */ +ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, + u_int8_t next_header, chunk_t data); + #endif /** IP_PACKET_H_ @}*/ From 46bb36980b22853bf2469d412ba593c197308f81 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 15 Jul 2014 13:51:49 +0200 Subject: [PATCH 04/15] ip_packet: Add getter for IP payload --- src/libipsec/ip_packet.c | 23 ++++++++++++++++++----- src/libipsec/ip_packet.h | 7 +++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index a846717efa..4ef8fd5b8e 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -55,6 +55,11 @@ struct private_ip_packet_t { */ chunk_t packet; + /** + * IP payload (points into packet) + */ + chunk_t payload; + /** * IP version */ @@ -91,6 +96,12 @@ METHOD(ip_packet_t, get_encoding, chunk_t, return this->packet; } +METHOD(ip_packet_t, get_payload, chunk_t, + private_ip_packet_t *this) +{ + return this->payload; +} + METHOD(ip_packet_t, get_next_header, u_int8_t, private_ip_packet_t *this) { @@ -163,6 +174,7 @@ ip_packet_t *ip_packet_create(chunk_t packet) u_int8_t version, next_header; u_int16_t sport = 0, dport = 0; host_t *src, *dst; + chunk_t payload; if (packet.len < 1) { @@ -186,9 +198,8 @@ ip_packet_t *ip_packet_create(chunk_t packet) ip = (struct ip*)packet.ptr; /* remove any RFC 4303 TFC extra padding */ packet.len = min(packet.len, untoh16(&ip->ip_len)); - - if (!parse_transport_header(chunk_skip(packet, ip->ip_hl * 4), - ip->ip_p, &sport, &dport)) + payload = chunk_skip(packet, ip->ip_hl * 4); + if (!parse_transport_header(payload, ip->ip_p, &sport, &dport)) { goto failed; } @@ -214,8 +225,8 @@ ip_packet_t *ip_packet_create(chunk_t packet) packet.len = min(packet.len, untoh16(&ip->ip6_plen)); /* we only handle packets without extension headers, just skip the * basic IPv6 header */ - if (!parse_transport_header(chunk_skip(packet, 40), ip->ip6_nxt, - &sport, &dport)) + payload = chunk_skip(packet, 40); + if (!parse_transport_header(payload, ip->ip6_nxt, &sport, &dport)) { goto failed; } @@ -239,12 +250,14 @@ ip_packet_t *ip_packet_create(chunk_t packet) .get_destination = _get_destination, .get_next_header = _get_next_header, .get_encoding = _get_encoding, + .get_payload = _get_payload, .clone = _clone_, .destroy = _destroy, }, .src = src, .dst = dst, .packet = packet, + .payload = payload, .version = version, .next_header = next_header, ); diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h index 0f44c89517..7f7c1a314a 100644 --- a/src/libipsec/ip_packet.h +++ b/src/libipsec/ip_packet.h @@ -67,6 +67,13 @@ struct ip_packet_t { */ chunk_t (*get_encoding)(ip_packet_t *this); + /** + * Get only the payload + * + * @return IP payload (internal data) + */ + chunk_t (*get_payload)(ip_packet_t *this); + /** * Clone the IP packet * From 108a67893f0a32ecee44742c452964e03c0a6c63 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 15 Jul 2014 17:19:48 +0200 Subject: [PATCH 05/15] ip_packet: Apply transport protocol ports when encoding IP packet --- src/libipsec/ip_packet.c | 33 +++++++++++++++++++++++++-------- src/libipsec/ip_packet.h | 7 +++++-- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index 4ef8fd5b8e..7de4658b9e 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -317,12 +317,15 @@ static u_int16_t pseudo_header_checksum(host_t *src, host_t *dst, } /** - * Calculate transport header checksums + * Apply transport ports and calculate header checksums */ -static void fix_transport_checksum(host_t *src, host_t *dst, u_int8_t proto, - chunk_t payload) +static void fix_transport_header(host_t *src, host_t *dst, u_int8_t proto, + chunk_t payload) { - u_int16_t sum = 0; + u_int16_t sum = 0, sport, dport; + + sport = src->get_port(src); + dport = dst->get_port(dst); switch (proto) { @@ -335,6 +338,14 @@ static void fix_transport_checksum(host_t *src, host_t *dst, u_int8_t proto, return; } udp = (struct udphdr*)payload.ptr; + if (sport != 0) + { + udp->source = htons(sport); + } + if (dport != 0) + { + udp->dest = htons(dport); + } udp->check = 0; sum = pseudo_header_checksum(src, dst, proto, payload); udp->check = chunk_internet_checksum_inc(payload, sum); @@ -349,6 +360,14 @@ static void fix_transport_checksum(host_t *src, host_t *dst, u_int8_t proto, return; } tcp = (struct tcphdr*)payload.ptr; + if (sport != 0) + { + tcp->source = htons(sport); + } + if (dport != 0) + { + tcp->dest = htons(dport); + } tcp->check = 0; sum = pseudo_header_checksum(src, dst, proto, payload); tcp->check = chunk_internet_checksum_inc(payload, sum); @@ -391,8 +410,7 @@ ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, ip.ip_sum = chunk_internet_checksum(chunk_from_thing(ip)); packet = chunk_cat("cc", chunk_from_thing(ip), data); - fix_transport_checksum(src, dst, next_header, - chunk_skip(packet, 20)); + fix_transport_header(src, dst, next_header, chunk_skip(packet, 20)); return ip_packet_create(packet); } #ifdef HAVE_NETINET_IP6_H @@ -408,8 +426,7 @@ ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, memcpy(&ip.ip6_dst, dst->get_address(dst).ptr, sizeof(ip.ip6_dst)); packet = chunk_cat("cc", chunk_from_thing(ip), data); - fix_transport_checksum(src, dst, next_header, - chunk_skip(packet, 40)); + fix_transport_header(src, dst, next_header, chunk_skip(packet, 40)); return ip_packet_create(packet); } #endif /* HAVE_NETINET_IP6_H */ diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h index 7f7c1a314a..73721cfd1d 100644 --- a/src/libipsec/ip_packet.h +++ b/src/libipsec/ip_packet.h @@ -103,8 +103,11 @@ ip_packet_t *ip_packet_create(chunk_t packet); /** * Encode an IP packet from the given data. * - * @param src source address (cloned) - * @param dst destination address (cloned) + * If src and/or dst have ports set they are applied to UDP/TCP headers found + * in the packet. + * + * @param src source address and optional port (cloned) + * @param dst destination address and optional port (cloned) * @param next_header the protocol (IPv4) or next header (IPv6) * @param data complete data after basic IP header (cloned) * @return ip_packet_t instance, or NULL if invalid From 16e519d42c807aa2397c77d5f294423fd4bafc0b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 15 Jul 2014 17:32:25 +0200 Subject: [PATCH 06/15] ip_packet: Add function to easily encode UDP packets --- src/libipsec/ip_packet.c | 18 ++++++++++++++++++ src/libipsec/ip_packet.h | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index 7de4658b9e..f6e08c0cb4 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -435,3 +435,21 @@ ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, return NULL; } } + +/** + * Described in header. + */ +ip_packet_t *ip_packet_create_udp_from_data(host_t *src, host_t *dst, + chunk_t data) +{ + struct udphdr udp = { + .len = htons(8 + data.len), + .check = 0, + }; + ip_packet_t *packet; + + data = chunk_cat("cc", chunk_from_thing(udp), data); + packet = ip_packet_create_from_data(src, dst, IPPROTO_UDP, data); + chunk_free(&data); + return packet; +} diff --git a/src/libipsec/ip_packet.h b/src/libipsec/ip_packet.h index 73721cfd1d..fa38eac2cc 100644 --- a/src/libipsec/ip_packet.h +++ b/src/libipsec/ip_packet.h @@ -115,4 +115,15 @@ ip_packet_t *ip_packet_create(chunk_t packet); ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, u_int8_t next_header, chunk_t data); +/** + * Encode a UDP packet from the given data. + * + * @param src source address and port (cloned) + * @param dst destination address and port (cloned) + * @param data UDP data (cloned) + * @return ip_packet_t instance, or NULL if invalid + */ +ip_packet_t *ip_packet_create_udp_from_data(host_t *src, host_t *dst, + chunk_t data); + #endif /** IP_PACKET_H_ @}*/ From 2dc26c557e6c8da38b60f8154acf0b007747253c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 15 Jul 2014 17:52:43 +0200 Subject: [PATCH 07/15] android: Add DNS proxy implementation This class proxies DNS requests over VPN-protected UDP sockets. It is not really Android specific and might be useful for kernel-libipsec or libipsec in general too, so we could maybe move it later to libipsec (might need some portability work). --- .../android/jni/libandroidbridge/Android.mk | 1 + .../backend/android_dns_proxy.c | 309 ++++++++++++++++++ .../backend/android_dns_proxy.h | 78 +++++ 3 files changed, 388 insertions(+) create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c create mode 100644 src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h diff --git a/src/frontends/android/jni/libandroidbridge/Android.mk b/src/frontends/android/jni/libandroidbridge/Android.mk index fbe56d5b41..9c4561c3a4 100644 --- a/src/frontends/android/jni/libandroidbridge/Android.mk +++ b/src/frontends/android/jni/libandroidbridge/Android.mk @@ -6,6 +6,7 @@ LOCAL_SRC_FILES := \ android_jni.c \ backend/android_attr.c \ backend/android_creds.c \ +backend/android_dns_proxy.c \ backend/android_private_key.c \ backend/android_service.c \ charonservice.c \ diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c new file mode 100644 index 0000000000..9f5170b1df --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include +#include + +#include "android_dns_proxy.h" + +#include +#include +#include +#include + +/** + * Timeout in seconds for sockets (i.e. not used for x seconds -> delete) + */ +#define SOCKET_TIMEOUT 30 + +typedef struct private_android_dns_proxy_t private_android_dns_proxy_t; + +struct private_android_dns_proxy_t { + + /** + * Public interface + */ + android_dns_proxy_t public; + + /** + * Mapping from source address to sockets + */ + hashtable_t *sockets; + + /** + * Registered callback + */ + dns_proxy_response_cb_t cb; + + /** + * Data passed to callback + */ + void *data; + + /** + * Lock used to synchronize access to the private members + */ + rwlock_t *lock; +}; + +/** + * Data for proxy sockets + */ +typedef struct { + private_android_dns_proxy_t *proxy; + time_t last_use; + host_t *src; + int fd; +} proxy_socket_t; + +/** + * Destroy a socket + */ +static void socket_destroy(proxy_socket_t *this) +{ + this->src->destroy(this->src); + if (this->fd != -1) + { + close(this->fd); + } + free(this); +} + +/** + * Hash a proxy socket by src address + */ +static u_int socket_hash(host_t *src) +{ + u_int16_t port = src->get_port(src); + return chunk_hash_inc(src->get_address(src), + chunk_hash(chunk_from_thing(port))); +} + +/** + * Compare proxy sockets by src address + */ +static bool socket_equals(host_t *a, host_t *b) +{ + return a->equals(a, b); +} + +/** + * Opens a UDP socket for the given address family + */ +static int open_socket(int family) +{ + int skt; + + skt = socket(family, SOCK_DGRAM, IPPROTO_UDP); + if (skt < 0) + { + DBG1(DBG_NET, "could not open proxy socket: %s", strerror(errno)); + return -1; + } + if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface, + skt, family)) + { + DBG1(DBG_NET, "installing bypass policy for proxy socket failed"); + } + return skt; +} + +/** + * Create a proxy socket for the given source + */ +static proxy_socket_t *create_socket(private_android_dns_proxy_t *this, + host_t *src) +{ + proxy_socket_t *skt; + + INIT(skt, + .proxy = this, + .src = src->clone(src), + .fd = open_socket(src->get_family(src)), + ); + if (skt->fd == -1) + { + socket_destroy(skt); + return NULL; + } + return skt; +} + +CALLBACK(handle_response, bool, + proxy_socket_t *this, int fd, watcher_event_t event) +{ + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + char buf[4096]; + ssize_t len; + host_t *src; + + len = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&addr, + &addr_len); + if (len > 0) + { + ip_packet_t *packet; + + src = host_create_from_sockaddr((sockaddr_t*)&addr); + if (!src) + { + DBG1(DBG_NET, "failed to parse source address"); + return TRUE; + } + packet = ip_packet_create_udp_from_data(src, this->src, + chunk_create(buf, len)); + if (!packet) + { + DBG1(DBG_NET, "failed to parse DNS response"); + return TRUE; + } + this->proxy->lock->read_lock(this->proxy->lock); + this->last_use = time_monotonic(NULL); + if (this->proxy->cb) + { + this->proxy->cb(this->proxy->data, packet); + } + else + { + packet->destroy(packet); + } + this->proxy->lock->unlock(this->proxy->lock); + } + else if (errno != EWOULDBLOCK) + { + DBG1(DBG_NET, "receiving DNS response failed: %s", strerror(errno)); + } + return TRUE; +} + +CALLBACK(handle_timeout, job_requeue_t, + proxy_socket_t *this) +{ + time_t now, diff; + + now = time_monotonic(NULL); + this->proxy->lock->write_lock(this->proxy->lock); + diff = now - this->last_use; + if (diff >= SOCKET_TIMEOUT) + { + this->proxy->sockets->remove(this->proxy->sockets, this->src); + lib->watcher->remove(lib->watcher, this->fd); + this->proxy->lock->unlock(this->proxy->lock); + socket_destroy(this); + return JOB_REQUEUE_NONE; + } + this->proxy->lock->unlock(this->proxy->lock); + return JOB_RESCHEDULE(SOCKET_TIMEOUT - diff); +} + +METHOD(android_dns_proxy_t, handle, bool, + private_android_dns_proxy_t *this, ip_packet_t *packet) +{ + proxy_socket_t *skt; + host_t *dst, *src; + chunk_t data; + + if (packet->get_next_header(packet) != IPPROTO_UDP) + { + return FALSE; + } + dst = packet->get_destination(packet); + if (dst->get_port(dst) != 53) + { /* no DNS packet */ + return FALSE; + } + src = packet->get_source(packet); + this->lock->write_lock(this->lock); + skt = this->sockets->get(this->sockets, src); + if (!skt) + { + skt = create_socket(this, src); + if (!skt) + { + this->lock->unlock(this->lock); + return FALSE; + } + this->sockets->put(this->sockets, skt->src, skt); + lib->watcher->add(lib->watcher, skt->fd, WATCHER_READ, handle_response, + skt); + lib->scheduler->schedule_job(lib->scheduler, + (job_t*)callback_job_create(handle_timeout, skt, + NULL, (callback_job_cancel_t)return_false), SOCKET_TIMEOUT); + } + skt->last_use = time_monotonic(NULL); + data = packet->get_payload(packet); + /* remove UDP header */ + data = chunk_skip(data, 8); + if (sendto(skt->fd, data.ptr, data.len, 0, dst->get_sockaddr(dst), + *dst->get_sockaddr_len(dst)) != data.len) + { + DBG1(DBG_NET, "sending DNS request failed: %s", strerror(errno)); + } + this->lock->unlock(this->lock); + return TRUE; +} + +METHOD(android_dns_proxy_t, register_cb, void, + private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb, void *data) +{ + this->lock->write_lock(this->lock); + this->cb = cb; + this->data = data; + this->lock->unlock(this->lock); +} + +METHOD(android_dns_proxy_t, unregister_cb, void, + private_android_dns_proxy_t *this, dns_proxy_response_cb_t cb) +{ + this->lock->write_lock(this->lock); + if (this->cb == cb) + { + this->cb = NULL; + } + this->lock->unlock(this->lock); +} + +METHOD(android_dns_proxy_t, destroy, void, + private_android_dns_proxy_t *this) +{ + this->sockets->destroy_function(this->sockets, (void*)socket_destroy); + this->lock->destroy(this->lock); + free(this); +} + +/** + * Described in header. + */ +android_dns_proxy_t *android_dns_proxy_create() +{ + private_android_dns_proxy_t *this; + + INIT(this, + .public = { + .handle = _handle, + .register_cb = _register_cb, + .unregister_cb = _unregister_cb, + .destroy = _destroy, + }, + .sockets = hashtable_create((hashtable_hash_t)socket_hash, + (hashtable_equals_t)socket_equals, 4), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + ); + + return &this->public; +} diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h new file mode 100644 index 0000000000..a906f7c71f --- /dev/null +++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup android_dns_proxy android_dns_proxy + * @{ @ingroup android_backend + */ + +#ifndef ANDROID_DNS_PROXY_H_ +#define ANDROID_DNS_PROXY_H_ + +#include + +typedef struct android_dns_proxy_t android_dns_proxy_t; + +/** + * Callback called to deliver a DNS response packet. + * + * @param data data supplied during registration of the callback + * @param packet DNS response packet (has to be destroyed) + */ +typedef void (*dns_proxy_response_cb_t)(void *data, ip_packet_t *packet); + +/** + * DNS proxy class + */ +struct android_dns_proxy_t { + + /** + * Handle an outbound DNS packet (if the packet is one) + * + * @param packet packet to handle + * @return TRUE if handled, FALSE otherwise (no DNS) + */ + bool (*handle)(android_dns_proxy_t *this, ip_packet_t *packet); + + /** + * Register the callback used to deliver DNS response packets. + * + * @param cb the callback function + * @param data optional data provided to callback + */ + void (*register_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb, + void *data); + + /** + * Unregister the callback used to deliver DNS response packets. + * + * @param cb the callback function + * @param data optional data provided to callback + */ + void (*unregister_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb); + + /** + * Destroy an instance. + */ + void (*destroy)(android_dns_proxy_t *this); +}; + +/** + * Create an instance. + */ +android_dns_proxy_t *android_dns_proxy_create(); + +#endif /** ANDROID_DNS_PROXY_H_ @}*/ + From 614359a7d5bedb5d750bac33fdd4b335dde451e5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 16 Jul 2014 12:38:44 +0200 Subject: [PATCH 08/15] bus: Add ike_reestablish_pre hook, called before DNS resolution The old hook is renamed to ike_reestablish_post and is now also called when the initiation of the new IKE_SA failed. --- .../backend/android_service.c | 9 +++-- src/libcharon/bus/bus.c | 37 +++++++++++++++++-- src/libcharon/bus/bus.h | 17 +++++++-- src/libcharon/bus/listeners/listener.h | 22 +++++++++-- src/libcharon/sa/ike_sa.c | 6 ++- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index d73dc45825..a6a24dba97 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -445,10 +445,11 @@ METHOD(listener_t, ike_rekey, bool, return TRUE; } -METHOD(listener_t, ike_reestablish, bool, - private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +METHOD(listener_t, ike_reestablish_post, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, + bool initiated) { - if (this->ike_sa == old) + if (this->ike_sa == old && initiated) { this->ike_sa = new; /* re-register hook to detect initiation failures */ @@ -655,7 +656,7 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, .public = { .listener = { .ike_rekey = _ike_rekey, - .ike_reestablish = _ike_reestablish, + .ike_reestablish_post = _ike_reestablish_post, .ike_updown = _ike_updown, .child_updown = _child_updown, .alert = _alert, diff --git a/src/libcharon/bus/bus.c b/src/libcharon/bus/bus.c index d1c138cd19..cb59f976bd 100644 --- a/src/libcharon/bus/bus.c +++ b/src/libcharon/bus/bus.c @@ -755,7 +755,7 @@ METHOD(bus_t, ike_rekey, void, this->mutex->unlock(this->mutex); } -METHOD(bus_t, ike_reestablish, void, +METHOD(bus_t, ike_reestablish_pre, void, private_bus_t *this, ike_sa_t *old, ike_sa_t *new) { enumerator_t *enumerator; @@ -766,12 +766,40 @@ METHOD(bus_t, ike_reestablish, void, enumerator = this->listeners->create_enumerator(this->listeners); while (enumerator->enumerate(enumerator, &entry)) { - if (entry->calling || !entry->listener->ike_reestablish) + if (entry->calling || !entry->listener->ike_reestablish_pre) { continue; } entry->calling++; - keep = entry->listener->ike_reestablish(entry->listener, old, new); + keep = entry->listener->ike_reestablish_pre(entry->listener, old, new); + entry->calling--; + if (!keep) + { + unregister_listener(this, entry, enumerator); + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); +} + +METHOD(bus_t, ike_reestablish_post, void, + private_bus_t *this, ike_sa_t *old, ike_sa_t *new, bool initiated) +{ + enumerator_t *enumerator; + entry_t *entry; + bool keep; + + this->mutex->lock(this->mutex); + enumerator = this->listeners->create_enumerator(this->listeners); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->calling || !entry->listener->ike_reestablish_post) + { + continue; + } + entry->calling++; + keep = entry->listener->ike_reestablish_post(entry->listener, old, new, + initiated); entry->calling--; if (!keep) { @@ -978,7 +1006,8 @@ bus_t *bus_create() .child_keys = _child_keys, .ike_updown = _ike_updown, .ike_rekey = _ike_rekey, - .ike_reestablish = _ike_reestablish, + .ike_reestablish_pre = _ike_reestablish_pre, + .ike_reestablish_post = _ike_reestablish_post, .child_updown = _child_updown, .child_rekey = _child_rekey, .authorize = _authorize, diff --git a/src/libcharon/bus/bus.h b/src/libcharon/bus/bus.h index 1d708c5a51..1a6711a411 100644 --- a/src/libcharon/bus/bus.h +++ b/src/libcharon/bus/bus.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2014 Tobias Brunner * Copyright (C) 2006-2009 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -380,12 +380,23 @@ struct bus_t { void (*ike_rekey)(bus_t *this, ike_sa_t *old, ike_sa_t *new); /** - * IKE_SA reestablishing hook. + * IKE_SA reestablishing hook (before resolving hosts). * * @param old reestablished and obsolete IKE_SA * @param new new IKE_SA replacing old */ - void (*ike_reestablish)(bus_t *this, ike_sa_t *old, ike_sa_t *new); + void (*ike_reestablish_pre)(bus_t *this, ike_sa_t *old, ike_sa_t *new); + + /** + * IKE_SA reestablishing hook (after configuring and initiating the new + * IKE_SA). + * + * @param old reestablished and obsolete IKE_SA + * @param new new IKE_SA replacing old + * @param initiated TRUE if initiated successfully, FALSE otherwise + */ + void (*ike_reestablish_post)(bus_t *this, ike_sa_t *old, ike_sa_t *new, + bool initiated); /** * CHILD_SA up/down hook. diff --git a/src/libcharon/bus/listeners/listener.h b/src/libcharon/bus/listeners/listener.h index abcc765e59..0910cb3617 100644 --- a/src/libcharon/bus/listeners/listener.h +++ b/src/libcharon/bus/listeners/listener.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2011-2014 Tobias Brunner * Copyright (C) 2009 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -129,14 +130,29 @@ struct listener_t { /** * Hook called when an initiator reestablishes an IKE_SA. * - * This is invoked right before the new IKE_SA is checked in after - * initiating it. It is not invoked on the responder. + * This is invoked right after creating the new IKE_SA and setting the + * peer_cfg (and the old hosts), but before resolving the hosts anew. + * It is not invoked on the responder. * * @param old IKE_SA getting reestablished (is destroyed) * @param new new IKE_SA replacing old (gets established) * @return TRUE to stay registered, FALSE to unregister */ - bool (*ike_reestablish)(listener_t *this, ike_sa_t *old, ike_sa_t *new); + bool (*ike_reestablish_pre)(listener_t *this, ike_sa_t *old, ike_sa_t *new); + + /** + * Hook called when an initiator reestablishes an IKE_SA. + * + * This is invoked right before the new IKE_SA is checked in after + * initiating it. It is not invoked on the responder. + * + * @param old IKE_SA getting reestablished (is destroyed) + * @param new new IKE_SA replacing old (gets established) + * @param initiated TRUE if initiation was successful, FALSE otherwise + * @return TRUE to stay registered, FALSE to unregister + */ + bool (*ike_reestablish_post)(listener_t *this, ike_sa_t *old, + ike_sa_t *new, bool initiated); /** * Hook called when a CHILD_SA gets up or down. diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index c338cdaefa..fddd83c63b 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -1650,6 +1650,7 @@ METHOD(ike_sa_t, reestablish, status_t, new->set_other_host(new, host->clone(host)); host = this->my_host; new->set_my_host(new, host->clone(host)); + charon->bus->ike_reestablish_pre(charon->bus, &this->public, new); /* resolve hosts but use the old addresses above as fallback */ resolve_hosts((private_ike_sa_t*)new); /* if we already have a virtual IP, we reuse it */ @@ -1734,12 +1735,15 @@ METHOD(ike_sa_t, reestablish, status_t, if (status == DESTROY_ME) { + charon->bus->ike_reestablish_post(charon->bus, &this->public, new, + FALSE); charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new); status = FAILED; } else { - charon->bus->ike_reestablish(charon->bus, &this->public, new); + charon->bus->ike_reestablish_post(charon->bus, &this->public, new, + TRUE); charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); status = SUCCESS; } From cc1712a8f4656f0e2417da44f3b27385e71ffa17 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 16 Jul 2014 13:11:10 +0200 Subject: [PATCH 09/15] android: Use DNS proxy when reestablishing IKE_SAs --- .../backend/android_service.c | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index a6a24dba97..113056532e 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 Tobias Brunner + * Copyright (C) 2010-2014 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -19,6 +19,7 @@ #include #include "android_service.h" +#include "android_dns_proxy.h" #include "../charonservice.h" #include "../vpnservice_builder.h" @@ -83,6 +84,15 @@ struct private_android_service_t { */ int tunfd; + /** + * DNS proxy + */ + android_dns_proxy_t *dns_proxy; + + /** + * Whether to use the DNS proxy or not + */ + bool use_dns_proxy; }; /** @@ -143,7 +153,7 @@ static job_requeue_t handle_plain(private_android_service_t *this) fd_set set; ssize_t len; int tunfd; - bool old; + bool old, dns_proxy; timeval_t tv = { /* check every second if tunfd is still valid */ .tv_sec = 1, @@ -159,6 +169,8 @@ static job_requeue_t handle_plain(private_android_service_t *this) } tunfd = this->tunfd; FD_SET(tunfd, &set); + /* cache this while we have the lock */ + dns_proxy = this->use_dns_proxy; this->lock->unlock(this->lock); old = thread_cancelability(TRUE); @@ -192,7 +204,10 @@ static job_requeue_t handle_plain(private_android_service_t *this) packet = ip_packet_create(raw); if (packet) { - ipsec->processor->queue_outbound(ipsec->processor, packet); + if (!dns_proxy || !this->dns_proxy->handle(this->dns_proxy, packet)) + { + ipsec->processor->queue_outbound(ipsec->processor, packet); + } } else { @@ -324,6 +339,8 @@ static bool setup_tun_device(private_android_service_t *this, (ipsec_inbound_cb_t)deliver_plain, this); ipsec->processor->register_outbound(ipsec->processor, (ipsec_outbound_cb_t)send_esp, NULL); + this->dns_proxy->register_cb(this->dns_proxy, + (dns_proxy_response_cb_t)deliver_plain, this); lib->processor->queue_job(lib->processor, (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this, @@ -349,6 +366,8 @@ static void close_tun_device(private_android_service_t *this) this->tunfd = -1; this->lock->unlock(this->lock); + this->dns_proxy->unregister_cb(this->dns_proxy, + (dns_proxy_response_cb_t)deliver_plain); ipsec->processor->unregister_outbound(ipsec->processor, (ipsec_outbound_cb_t)send_esp); ipsec->processor->unregister_inbound(ipsec->processor, @@ -368,6 +387,11 @@ METHOD(listener_t, child_updown, bool, { /* disable the hooks registered to catch initiation failures */ this->public.listener.ike_updown = NULL; + /* CHILD_SA is up so we can disable the DNS proxy we enabled to + * reestablish the SA */ + this->lock->write_lock(this->lock); + this->use_dns_proxy = FALSE; + this->lock->unlock(this->lock); if (!setup_tun_device(this, ike_sa, child_sa)) { DBG1(DBG_DMN, "failed to setup TUN device"); @@ -445,6 +469,20 @@ METHOD(listener_t, ike_rekey, bool, return TRUE; } +METHOD(listener_t, ike_reestablish_pre, bool, + private_android_service_t *this, ike_sa_t *old, ike_sa_t *new) +{ + if (this->ike_sa == old) + { + /* enable DNS proxy so hosts are properly resolved while the TUN device + * is still active */ + this->lock->write_lock(this->lock); + this->use_dns_proxy = TRUE; + this->lock->unlock(this->lock); + } + return TRUE; +} + METHOD(listener_t, ike_reestablish_post, bool, private_android_service_t *this, ike_sa_t *old, ike_sa_t *new, bool initiated) @@ -459,7 +497,6 @@ METHOD(listener_t, ike_reestablish_post, bool, * get ignored and thus we trigger the event here */ charonservice->update_status(charonservice, CHARONSERVICE_CHILD_STATE_DOWN); - /* the TUN device will be closed when the new CHILD_SA is established */ } return TRUE; } @@ -631,6 +668,7 @@ METHOD(android_service_t, destroy, void, charon->bus->remove_listener(charon->bus, &this->public.listener); /* make sure the tun device is actually closed */ close_tun_device(this); + this->dns_proxy->destroy(this->dns_proxy); this->lock->destroy(this->lock); free(this->type); free(this->gateway); @@ -656,6 +694,7 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, .public = { .listener = { .ike_rekey = _ike_rekey, + .ike_reestablish_pre = _ike_reestablish_pre, .ike_reestablish_post = _ike_reestablish_post, .ike_updown = _ike_updown, .child_updown = _child_updown, @@ -664,6 +703,7 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, .destroy = _destroy, }, .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .dns_proxy = android_dns_proxy_create(), .username = username, .password = password, .gateway = gateway, From 36aab70ab068a8552cf3bc4f80187fc178a2aaf7 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 16 Jul 2014 13:54:57 +0200 Subject: [PATCH 10/15] android: Add method to BuilderAdapter to re-establish without DNS-related data Non-DNS data is cached in the BuilderAdapter so the TUN device can be recreated easily (since the CHILD_SA is gone we couldn't actually gather that information). --- .../jni/libandroidbridge/vpnservice_builder.c | 23 ++++- .../jni/libandroidbridge/vpnservice_builder.h | 9 +- .../android/logic/CharonVpnService.java | 86 +++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c index 6b10228d09..c7a6eb6daf 100644 --- a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c +++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2014 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -197,8 +197,10 @@ failed: return FALSE; } -METHOD(vpnservice_builder_t, establish, int, - private_vpnservice_builder_t *this) +/** + * Establish or reestablish the TUN device + */ +static int establish_internal(private_vpnservice_builder_t *this, char *method) { JNIEnv *env; jmethodID method_id; @@ -209,7 +211,7 @@ METHOD(vpnservice_builder_t, establish, int, DBG2(DBG_LIB, "builder: building TUN device"); method_id = (*env)->GetMethodID(env, android_charonvpnservice_builder_class, - "establish", "()I"); + method, "()I"); if (!method_id) { goto failed; @@ -229,6 +231,18 @@ failed: return -1; } +METHOD(vpnservice_builder_t, establish, int, + private_vpnservice_builder_t *this) +{ + return establish_internal(this, "establish"); +} + +METHOD(vpnservice_builder_t, establish_no_dns, int, + private_vpnservice_builder_t *this) +{ + return establish_internal(this, "establishNoDns"); +} + METHOD(vpnservice_builder_t, destroy, void, private_vpnservice_builder_t *this) { @@ -252,6 +266,7 @@ vpnservice_builder_t *vpnservice_builder_create(jobject builder) .add_dns = _add_dns, .set_mtu = _set_mtu, .establish = _establish, + .establish_no_dns = _establish_no_dns, .destroy = _destroy, }, ); diff --git a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h index 209090896d..08c436da65 100644 --- a/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h +++ b/src/frontends/android/jni/libandroidbridge/vpnservice_builder.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2014 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager * Hochschule fuer Technik Rapperswil @@ -77,6 +77,13 @@ struct vpnservice_builder_t { */ int (*establish)(vpnservice_builder_t *this); + /** + * Build the TUN device without DNS related data + * + * @return the TUN file descriptor, -1 if failed + */ + int (*establish_no_dns)(vpnservice_builder_t *this); + /** * Destroy a vpnservice_builder */ 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 d53d478b22..5707f4fb61 100644 --- a/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java +++ b/src/frontends/android/src/org/strongswan/android/logic/CharonVpnService.java @@ -22,6 +22,7 @@ import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.List; import org.strongswan.android.data.VpnProfile; import org.strongswan.android.data.VpnProfileDataSource; @@ -526,11 +527,14 @@ public class CharonVpnService extends VpnService implements Runnable { private final String mName; private VpnService.Builder mBuilder; + private BuilderCache mCache; + private BuilderCache mEstablishedCache; public BuilderAdapter(String name) { mName = name; mBuilder = createBuilder(name); + mCache = new BuilderCache(); } private VpnService.Builder createBuilder(String name) @@ -553,6 +557,7 @@ public class CharonVpnService extends VpnService implements Runnable try { mBuilder.addAddress(address, prefixLength); + mCache.addAddress(address, prefixLength); } catch (IllegalArgumentException ex) { @@ -579,6 +584,7 @@ public class CharonVpnService extends VpnService implements Runnable try { mBuilder.addRoute(address, prefixLength); + mCache.addRoute(address, prefixLength); } catch (IllegalArgumentException ex) { @@ -605,6 +611,7 @@ public class CharonVpnService extends VpnService implements Runnable try { mBuilder.setMtu(mtu); + mCache.setMtu(mtu); } catch (IllegalArgumentException ex) { @@ -632,8 +639,87 @@ public class CharonVpnService extends VpnService implements Runnable /* now that the TUN device is created we don't need the current * builder anymore, but we might need another when reestablishing */ mBuilder = createBuilder(mName); + mEstablishedCache = mCache; + mCache = new BuilderCache(); return fd.detachFd(); } + + public synchronized int establishNoDns() + { + ParcelFileDescriptor fd; + + if (mEstablishedCache == null) + { + return -1; + } + try + { + Builder builder = createBuilder(mName); + mEstablishedCache.applyData(builder); + fd = builder.establish(); + } + catch (Exception ex) + { + ex.printStackTrace(); + return -1; + } + if (fd == null) + { + return -1; + } + return fd.detachFd(); + } + } + + /** + * Cache non DNS related information so we can recreate the builder without + * that information when reestablishing IKE_SAs + */ + public class BuilderCache + { + private final List mAddresses = new ArrayList(); + private final List mRoutes = new ArrayList(); + private int mMtu; + + public void addAddress(String address, int prefixLength) + { + mAddresses.add(new PrefixedAddress(address, prefixLength)); + } + + public void addRoute(String address, int prefixLength) + { + mRoutes.add(new PrefixedAddress(address, prefixLength)); + } + + public void setMtu(int mtu) + { + mMtu = mtu; + } + + public void applyData(VpnService.Builder builder) + { + for (PrefixedAddress address : mAddresses) + { + builder.addAddress(address.mAddress, address.mPrefix); + } + for (PrefixedAddress route : mRoutes) + { + builder.addRoute(route.mAddress, route.mPrefix); + } + builder.setMtu(mMtu); + } + + private class PrefixedAddress + { + public String mAddress; + public int mPrefix; + + public PrefixedAddress(String address, int prefix) + { + this.mAddress = address; + this.mPrefix = prefix; + } + } } /* From c66f5f844d0e19e5785e223e756d2fd6955e8f24 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 16 Jul 2014 14:01:12 +0200 Subject: [PATCH 11/15] android: Recreate the TUN device without DNS when reestablishing IKE_SAs This enables DNS resolution while reestablishing if the VPN gateway pushed DNS servers to the client that are only reachable via VPN. --- .../backend/android_service.c | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 113056532e..73738c22ed 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -349,6 +349,36 @@ static bool setup_tun_device(private_android_service_t *this, return TRUE; } +/** + * Setup a new TUN device based on the existing one, but without DNS server. + */ +static bool setup_tun_device_without_dns(private_android_service_t *this) +{ + vpnservice_builder_t *builder; + int tunfd; + + DBG1(DBG_DMN, "setting up TUN device without DNS"); + + builder = charonservice->get_vpnservice_builder(charonservice); + + tunfd = builder->establish_no_dns(builder); + if (tunfd == -1) + { + return FALSE; + } + + this->lock->write_lock(this->lock); + if (this->tunfd > 0) + { /* close previously opened TUN device, this should always be the case */ + close(this->tunfd); + } + this->tunfd = tunfd; + this->lock->unlock(this->lock); + + DBG1(DBG_DMN, "successfully created TUN device without DNS"); + return TRUE; +} + /** * Close the current tun device */ @@ -479,6 +509,14 @@ METHOD(listener_t, ike_reestablish_pre, bool, this->lock->write_lock(this->lock); this->use_dns_proxy = TRUE; this->lock->unlock(this->lock); + /* if DNS servers are installed that are only reachable through the VPN + * the DNS proxy doesn't help, so uninstall DNS servers */ + if (!setup_tun_device_without_dns(this)) + { + DBG1(DBG_DMN, "failed to setup TUN device without DNS"); + charonservice->update_status(charonservice, + CHARONSERVICE_GENERIC_ERROR); + } } return TRUE; } From e77f226a0fca9f7c8ced0c2276f3bb43a2aaa4c5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 16 Jul 2014 16:17:28 +0200 Subject: [PATCH 12/15] android: Add optional filter functionality to DNS proxy If specified only queries for a list of allowed host names will be proxied. --- .../backend/android_dns_proxy.c | 114 +++++++++++++++++- .../backend/android_dns_proxy.h | 8 ++ 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c index 9f5170b1df..045f2c1d1c 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "android_dns_proxy.h" @@ -59,6 +60,11 @@ struct private_android_dns_proxy_t { * Lock used to synchronize access to the private members */ rwlock_t *lock; + + /** + * Hostnames to filter queries by + */ + hashtable_t *hostnames; }; /** @@ -211,6 +217,88 @@ CALLBACK(handle_timeout, job_requeue_t, return JOB_RESCHEDULE(SOCKET_TIMEOUT - diff); } +/** + * DNS header and masks to access flags + */ +typedef struct __attribute__((packed)) { + u_int16_t id; + u_int16_t flags; +#define DNS_QR_MASK 0x8000 +#define DNS_OPCODE_MASK 0x7800 + u_int16_t qdcount; + u_int16_t ancount; + u_int16_t nscount; + u_int16_t arcount; +} dns_header_t; + +/** + * Extract the hostname in the question section data points to. + * Hostnames can be at most 255 characters (including dots separating labels), + * each label must be between 1 and 63 characters. + * The format is [len][label][len][label], followed by a null byte to indicate + * the null label of the root. + */ +static char *extract_hostname(chunk_t data) +{ + char *hostname, *pos, *end; + u_int8_t label; + + if (!data.len || data.len > 255) + { + return NULL; + } + label = *data.ptr; + data = chunk_skip(data, 1); + hostname = strndup(data.ptr, data.len); + /* replace all label lengths with dots */ + pos = hostname + label; + end = hostname + strlen(hostname); + while (pos < end) + { + label = *pos; + *pos++ = '.'; + pos += label; + } + return hostname; +} + +/** + * Check if the DNS query is for one of the allowed hostnames + */ +static bool check_hostname(private_android_dns_proxy_t *this, chunk_t data) +{ + dns_header_t *dns; + char *hostname; + bool success = FALSE; + + this->lock->read_lock(this->lock); + if (!this->hostnames->get_count(this->hostnames)) + { + this->lock->unlock(this->lock); + return TRUE; + } + if (data.len < sizeof(dns_header_t)) + { + this->lock->unlock(this->lock); + return FALSE; + } + dns = (dns_header_t*)data.ptr; + if ((ntohs(dns->flags) & DNS_QR_MASK) == 0 && + (ntohs(dns->flags) & DNS_OPCODE_MASK) == 0 && + dns->qdcount) + { + data = chunk_skip(data, sizeof(dns_header_t)); + hostname = extract_hostname(data); + if (hostname && this->hostnames->get(this->hostnames, hostname)) + { + success = TRUE; + } + free(hostname); + } + this->lock->unlock(this->lock); + return success; +} + METHOD(android_dns_proxy_t, handle, bool, private_android_dns_proxy_t *this, ip_packet_t *packet) { @@ -227,6 +315,13 @@ METHOD(android_dns_proxy_t, handle, bool, { /* no DNS packet */ return FALSE; } + data = packet->get_payload(packet); + /* remove UDP header */ + data = chunk_skip(data, 8); + if (!check_hostname(this, data)) + { + return FALSE; + } src = packet->get_source(packet); this->lock->write_lock(this->lock); skt = this->sockets->get(this->sockets, src); @@ -246,9 +341,6 @@ METHOD(android_dns_proxy_t, handle, bool, NULL, (callback_job_cancel_t)return_false), SOCKET_TIMEOUT); } skt->last_use = time_monotonic(NULL); - data = packet->get_payload(packet); - /* remove UDP header */ - data = chunk_skip(data, 8); if (sendto(skt->fd, data.ptr, data.len, 0, dst->get_sockaddr(dst), *dst->get_sockaddr_len(dst)) != data.len) { @@ -278,9 +370,22 @@ METHOD(android_dns_proxy_t, unregister_cb, void, this->lock->unlock(this->lock); } +METHOD(android_dns_proxy_t, add_hostname, void, + private_android_dns_proxy_t *this, char *hostname) +{ + char *existing; + + hostname = strdup(hostname); + this->lock->write_lock(this->lock); + existing = this->hostnames->put(this->hostnames, hostname, hostname); + this->lock->unlock(this->lock); + free(existing); +} + METHOD(android_dns_proxy_t, destroy, void, private_android_dns_proxy_t *this) { + this->hostnames->destroy_function(this->hostnames, (void*)free); this->sockets->destroy_function(this->sockets, (void*)socket_destroy); this->lock->destroy(this->lock); free(this); @@ -298,10 +403,13 @@ android_dns_proxy_t *android_dns_proxy_create() .handle = _handle, .register_cb = _register_cb, .unregister_cb = _unregister_cb, + .add_hostname = _add_hostname, .destroy = _destroy, }, .sockets = hashtable_create((hashtable_hash_t)socket_hash, (hashtable_equals_t)socket_equals, 4), + .hostnames = hashtable_create(hashtable_hash_str, + hashtable_equals_str, 4), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h index a906f7c71f..481b060cfa 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h +++ b/src/frontends/android/jni/libandroidbridge/backend/android_dns_proxy.h @@ -63,6 +63,14 @@ struct android_dns_proxy_t { */ void (*unregister_cb)(android_dns_proxy_t *this, dns_proxy_response_cb_t cb); + /** + * Add a hostname for which queries are proxied. If at least one hostname + * is configured DNS queries for others will not be handled. + * + * @param hostname hostname to add (gets cloned) + */ + void (*add_hostname)(android_dns_proxy_t *this, char *hostname); + /** * Destroy an instance. */ From 945832c67d139a2a7a3006c0b97894b78ee9caf9 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 16 Jul 2014 16:20:00 +0200 Subject: [PATCH 13/15] android: Only allow DNS queries for the configured hostname --- .../android/jni/libandroidbridge/backend/android_service.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 73738c22ed..fb8f93311e 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -749,6 +749,8 @@ android_service_t *android_service_create(android_creds_t *creds, char *type, .type = type, .tunfd = -1, ); + /* only allow queries for the VPN gateway */ + this->dns_proxy->add_hostname(this->dns_proxy, gateway); charon->bus->add_listener(charon->bus, &this->public.listener); From 5fd9e5fd009995ce0b6fed738efc00d90a0d1f89 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 17 Jul 2014 15:22:29 +0200 Subject: [PATCH 14/15] android: Terminate IKE_SA if initial IKE_SA_INIT fails Since VpnStateService.disconnect() is now not called until the error dialog is dismissed the daemon would continue to try connecting. So while the error dialog is shown the connection might actually be successfully established in the background, which is not intended. This way the IKE_SA is destroyed right after sending the IKE_SA_INIT of the second connection attempt (due to keyingtries=0). --- .../backend/android_service.c | 24 ++++++++++++++++++- 1 file changed, 23 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 fb8f93311e..5a85d30264 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -407,6 +407,17 @@ static void close_tun_device(private_android_service_t *this) close(tunfd); } +/** + * Terminate the IKE_SA with the given unique ID + */ +CALLBACK(terminate, job_requeue_t, + u_int32_t *id) +{ + charon->controller->terminate_ike(charon->controller, *id, + controller_cb_empty, NULL, 0); + return JOB_REQUEUE_NONE; +} + METHOD(listener_t, child_updown, bool, private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, bool up) @@ -476,9 +487,20 @@ METHOD(listener_t, alert, bool, case ALERT_PEER_INIT_UNREACHABLE: this->lock->read_lock(this->lock); if (this->tunfd < 0) - { /* only handle this if we are not reestablishing the SA */ + { + u_int32_t *id = malloc_thing(u_int32_t); + + /* always fail if we are not able to initiate the IKE_SA + * initially */ charonservice->update_status(charonservice, CHARONSERVICE_UNREACHABLE_ERROR); + /* terminate the IKE_SA so no further keying tries are + * attempted */ + *id = ike_sa->get_unique_id(ike_sa); + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create_with_prio( + (callback_job_cb_t)terminate, id, free, + (callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); } this->lock->unlock(this->lock); break; From ffff7219ef6af21c9497af8db49bfb3c1c9a3036 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 17 Jul 2014 15:39:29 +0200 Subject: [PATCH 15/15] android: For keyingtries > 0 notify the GUI if the limit is reached when reestablishing The IKE_SA is destroyed anyway, so letting the GUI remain in "connecting" state would be incorrect. We still use keyingtries=0 for now, though. And we still abort after the first failed attempt initially, in case there is a configuration error. --- .../libandroidbridge/backend/android_service.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/frontends/android/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/jni/libandroidbridge/backend/android_service.c index 5a85d30264..e60c491c16 100644 --- a/src/frontends/android/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/jni/libandroidbridge/backend/android_service.c @@ -502,6 +502,23 @@ METHOD(listener_t, alert, bool, (callback_job_cb_t)terminate, id, free, (callback_job_cancel_t)return_false, JOB_PRIO_HIGH)); } + else + { + peer_cfg_t *peer_cfg; + u_int32_t tries, try; + + /* when reestablishing and if keyingtries is not %forever + * the IKE_SA is destroyed after the set number of tries, + * so notify the GUI */ + peer_cfg = ike_sa->get_peer_cfg(ike_sa); + tries = peer_cfg->get_keyingtries(peer_cfg); + try = va_arg(args, u_int32_t); + if (tries != 0 && try == tries-1) + { + charonservice->update_status(charonservice, + CHARONSERVICE_UNREACHABLE_ERROR); + } + } this->lock->unlock(this->lock); break; default: