diff options
author | Tobias Brunner <tobias@strongswan.org> | 2014-07-22 11:10:59 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2014-07-22 11:14:00 +0200 |
commit | 1ddc1ec0b37355be22d55728557b88cde83292e6 (patch) | |
tree | 0c2eb284b44c121ca1b49d3d30dc71f9f42bf412 /src/libipsec/ip_packet.c | |
parent | 32109a535f3f0ae3e234ebfefc7c69dfc2327c67 (diff) | |
parent | ffff7219ef6af21c9497af8db49bfb3c1c9a3036 (diff) | |
download | strongswan-1ddc1ec0b37355be22d55728557b88cde83292e6.tar.bz2 strongswan-1ddc1ec0b37355be22d55728557b88cde83292e6.tar.xz |
Merge branch 'android-dns-proxy'
Adds a DNS proxy feature that uses VPN-protected sockets to resolve the
VPN gateway's hostname while reestablishing the IKE_SA, which is
required because we keep the TUN device up to avoid leaking plaintext
traffic.
The TUN device is recreated without DNS servers before reestablishing in
case the VPN server pushed DNS servers to the client that are only
reachable via VPN.
Fixes #622.
Diffstat (limited to 'src/libipsec/ip_packet.c')
-rw-r--r-- | src/libipsec/ip_packet.c | 271 |
1 files changed, 263 insertions, 8 deletions
diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c index 181cb88db..f6e08c0cb 100644 --- a/src/libipsec/ip_packet.c +++ b/src/libipsec/ip_packet.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2012-2014 Tobias Brunner * Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -22,6 +22,8 @@ #include <sys/types.h> #include <netinet/in.h> #include <netinet/ip.h> +#include <netinet/udp.h> +#include <netinet/tcp.h> #ifdef HAVE_NETINET_IP6_H #include <netinet/ip6.h> #endif @@ -54,6 +56,11 @@ struct private_ip_packet_t { chunk_t packet; /** + * IP payload (points into packet) + */ + chunk_t payload; + + /** * IP version */ u_int8_t version; @@ -89,6 +96,12 @@ METHOD(ip_packet_t, get_encoding, chunk_t, return this->packet; } +METHOD(ip_packet_t, get_payload, chunk_t, + private_ip_packet_t *this) +{ + return this->payload; +} + METHOD(ip_packet_t, get_next_header, u_int8_t, private_ip_packet_t *this) { @@ -111,13 +124,57 @@ METHOD(ip_packet_t, destroy, void, } /** + * Parse transport protocol header + */ +static bool parse_transport_header(chunk_t packet, u_int8_t proto, + u_int16_t *sport, u_int16_t *dport) +{ + switch (proto) + { + case IPPROTO_UDP: + { + struct udphdr *udp; + + if (packet.len < sizeof(*udp)) + { + DBG1(DBG_ESP, "UDP packet too short"); + return FALSE; + } + udp = (struct udphdr*)packet.ptr; + *sport = ntohs(udp->source); + *dport = ntohs(udp->dest); + break; + } + case IPPROTO_TCP: + { + struct tcphdr *tcp; + + if (packet.len < sizeof(*tcp)) + { + DBG1(DBG_ESP, "TCP packet too short"); + return FALSE; + } + tcp = (struct tcphdr*)packet.ptr; + *sport = ntohs(tcp->source); + *dport = ntohs(tcp->dest); + break; + } + default: + break; + } + return TRUE; +} + +/** * Described in header. */ ip_packet_t *ip_packet_create(chunk_t packet) { private_ip_packet_t *this; u_int8_t version, next_header; + u_int16_t sport = 0, dport = 0; host_t *src, *dst; + chunk_t payload; if (packet.len < 1) { @@ -141,11 +198,15 @@ ip_packet_t *ip_packet_create(chunk_t packet) ip = (struct ip*)packet.ptr; /* remove any RFC 4303 TFC extra padding */ packet.len = min(packet.len, untoh16(&ip->ip_len)); - + payload = chunk_skip(packet, ip->ip_hl * 4); + if (!parse_transport_header(payload, ip->ip_p, &sport, &dport)) + { + goto failed; + } src = host_create_from_chunk(AF_INET, - chunk_from_thing(ip->ip_src), 0); + chunk_from_thing(ip->ip_src), sport); dst = host_create_from_chunk(AF_INET, - chunk_from_thing(ip->ip_dst), 0); + chunk_from_thing(ip->ip_dst), dport); next_header = ip->ip_p; break; } @@ -154,7 +215,7 @@ ip_packet_t *ip_packet_create(chunk_t packet) { struct ip6_hdr *ip; - if (packet.len < sizeof(struct ip6_hdr)) + if (packet.len < sizeof(*ip)) { DBG1(DBG_ESP, "IPv6 packet too short"); goto failed; @@ -162,11 +223,17 @@ ip_packet_t *ip_packet_create(chunk_t packet) ip = (struct ip6_hdr*)packet.ptr; /* remove any RFC 4303 TFC extra padding */ packet.len = min(packet.len, untoh16(&ip->ip6_plen)); - + /* we only handle packets without extension headers, just skip the + * basic IPv6 header */ + payload = chunk_skip(packet, 40); + if (!parse_transport_header(payload, ip->ip6_nxt, &sport, &dport)) + { + goto failed; + } src = host_create_from_chunk(AF_INET6, - chunk_from_thing(ip->ip6_src), 0); + chunk_from_thing(ip->ip6_src), sport); dst = host_create_from_chunk(AF_INET6, - chunk_from_thing(ip->ip6_dst), 0); + chunk_from_thing(ip->ip6_dst), dport); next_header = ip->ip6_nxt; break; } @@ -183,12 +250,14 @@ ip_packet_t *ip_packet_create(chunk_t packet) .get_destination = _get_destination, .get_next_header = _get_next_header, .get_encoding = _get_encoding, + .get_payload = _get_payload, .clone = _clone_, .destroy = _destroy, }, .src = src, .dst = dst, .packet = packet, + .payload = payload, .version = version, .next_header = next_header, ); @@ -198,3 +267,189 @@ failed: chunk_free(&packet); return NULL; } + +/** + * Calculate the checksum for the pseudo IP header + */ +static u_int16_t pseudo_header_checksum(host_t *src, host_t *dst, + u_int8_t proto, chunk_t payload) +{ + switch (src->get_family(src)) + { + case AF_INET: + { + struct __attribute__((packed)) { + u_int32_t src; + u_int32_t dst; + u_char zero; + u_char proto; + u_int16_t len; + } pseudo = { + .proto = proto, + .len = htons(payload.len), + }; + memcpy(&pseudo.src, src->get_address(src).ptr, + sizeof(pseudo.src)); + memcpy(&pseudo.dst, dst->get_address(dst).ptr, + sizeof(pseudo.dst)); + return chunk_internet_checksum(chunk_from_thing(pseudo)); + } + case AF_INET6: + { + struct __attribute__((packed)) { + u_char src[16]; + u_char dst[16]; + u_int32_t len; + u_char zero[3]; + u_char next_header; + } pseudo = { + .next_header = proto, + .len = htons(payload.len), + }; + memcpy(&pseudo.src, src->get_address(src).ptr, + sizeof(pseudo.src)); + memcpy(&pseudo.dst, dst->get_address(dst).ptr, + sizeof(pseudo.dst)); + return chunk_internet_checksum(chunk_from_thing(pseudo)); + } + } + return 0xffff; +} + +/** + * Apply transport ports and calculate header checksums + */ +static void fix_transport_header(host_t *src, host_t *dst, u_int8_t proto, + chunk_t payload) +{ + u_int16_t sum = 0, sport, dport; + + sport = src->get_port(src); + dport = dst->get_port(dst); + + switch (proto) + { + case IPPROTO_UDP: + { + struct udphdr *udp; + + if (payload.len < sizeof(*udp)) + { + return; + } + udp = (struct udphdr*)payload.ptr; + if (sport != 0) + { + udp->source = htons(sport); + } + if (dport != 0) + { + udp->dest = htons(dport); + } + udp->check = 0; + sum = pseudo_header_checksum(src, dst, proto, payload); + udp->check = chunk_internet_checksum_inc(payload, sum); + break; + } + case IPPROTO_TCP: + { + struct tcphdr *tcp; + + if (payload.len < sizeof(*tcp)) + { + return; + } + tcp = (struct tcphdr*)payload.ptr; + if (sport != 0) + { + tcp->source = htons(sport); + } + if (dport != 0) + { + tcp->dest = htons(dport); + } + tcp->check = 0; + sum = pseudo_header_checksum(src, dst, proto, payload); + tcp->check = chunk_internet_checksum_inc(payload, sum); + break; + } + default: + break; + } +} + +/** + * Described in header. + */ +ip_packet_t *ip_packet_create_from_data(host_t *src, host_t *dst, + u_int8_t next_header, chunk_t data) +{ + chunk_t packet; + int family; + + family = src->get_family(src); + if (family != dst->get_family(dst)) + { + DBG1(DBG_ESP, "address family does not match"); + return NULL; + } + + switch (family) + { + case AF_INET: + { + struct ip ip = { + .ip_v = 4, + .ip_hl = 5, + .ip_len = htons(20 + data.len), + .ip_ttl = 0x80, + .ip_p = next_header, + }; + memcpy(&ip.ip_src, src->get_address(src).ptr, sizeof(ip.ip_src)); + memcpy(&ip.ip_dst, dst->get_address(dst).ptr, sizeof(ip.ip_dst)); + ip.ip_sum = chunk_internet_checksum(chunk_from_thing(ip)); + + packet = chunk_cat("cc", chunk_from_thing(ip), data); + fix_transport_header(src, dst, next_header, chunk_skip(packet, 20)); + return ip_packet_create(packet); + } +#ifdef HAVE_NETINET_IP6_H + case AF_INET6: + { + struct ip6_hdr ip = { + .ip6_flow = htonl(6), + .ip6_plen = htons(40 + data.len), + .ip6_nxt = next_header, + .ip6_hlim = 0x80, + }; + memcpy(&ip.ip6_src, src->get_address(src).ptr, sizeof(ip.ip6_src)); + memcpy(&ip.ip6_dst, dst->get_address(dst).ptr, sizeof(ip.ip6_dst)); + + packet = chunk_cat("cc", chunk_from_thing(ip), data); + fix_transport_header(src, dst, next_header, chunk_skip(packet, 40)); + return ip_packet_create(packet); + } +#endif /* HAVE_NETINET_IP6_H */ + default: + DBG1(DBG_ESP, "unsupported address family"); + return NULL; + } +} + +/** + * Described in header. + */ +ip_packet_t *ip_packet_create_udp_from_data(host_t *src, host_t *dst, + chunk_t data) +{ + struct udphdr udp = { + .len = htons(8 + data.len), + .check = 0, + }; + ip_packet_t *packet; + + data = chunk_cat("cc", chunk_from_thing(udp), data); + packet = ip_packet_create_from_data(src, dst, IPPROTO_UDP, data); + chunk_free(&data); + return packet; +} |