summaryrefslogtreecommitdiff
path: root/src/libipsec/ip_packet.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libipsec/ip_packet.c')
-rw-r--r--src/libipsec/ip_packet.c298
1 files changed, 289 insertions, 9 deletions
diff --git a/src/libipsec/ip_packet.c b/src/libipsec/ip_packet.c
index 181cb88db..0998efa9d 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
@@ -26,6 +26,33 @@
#include <netinet/ip6.h>
#endif
+/**
+ * TCP header, defined here because platforms disagree regarding member names
+ * and unfortunately Android does not define a variant with BSD names.
+ */
+struct tcphdr {
+ u_int16_t source;
+ u_int16_t dest;
+ u_int32_t seq;
+ u_int32_t ack_seq;
+ u_int16_t flags;
+ u_int16_t window;
+ u_int16_t check;
+ u_int16_t urg_ptr;
+} __attribute__((packed));
+
+/**
+ * UDP header, similar to the TCP header the system headers disagree on member
+ * names. Linux uses a union and on Android we could define __FAVOR_BSD to get
+ * the BSD member names, but this is simpler and more consistent with the above.
+ */
+struct udphdr {
+ u_int16_t source;
+ u_int16_t dest;
+ u_int16_t len;
+ u_int16_t check;
+} __attribute__((packed));
+
typedef struct private_ip_packet_t private_ip_packet_t;
/**
@@ -54,6 +81,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 +121,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 +149,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 +223,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,19 +240,25 @@ 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;
}
ip = (struct ip6_hdr*)packet.ptr;
/* remove any RFC 4303 TFC extra padding */
- packet.len = min(packet.len, untoh16(&ip->ip6_plen));
-
+ packet.len = min(packet.len, 40 + 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 +275,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 +292,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;
+}