Implement ICMP response for inactive IP address.

Send ICMP Host Unreachable packets back on the tun device
in reponse to a packet received for an IP address that is
not active in our pool (No active pdp context)

Only IPv4 implemented.

Change-Id: Ia2c708feab14bb4cada00b0a90e0cb56d680d1aa
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index 267637d..f2c9656 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -40,6 +40,7 @@
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
 #include <netinet/ip6.h>
 
 #include <osmocom/core/timer.h>
@@ -580,6 +581,72 @@
 	return 0;
 }
 
+static uint16_t inet_checksum(void *data, int len) {
+	int nleft = len;
+	int sum = 0;
+	unsigned short *w = data;
+	unsigned short answer = 0;
+
+	while (nleft > 1)
+	{
+	sum += *w++;
+	nleft -= 2;
+	}
+
+	if (nleft == 1)
+	{
+	*(unsigned char *)(&answer) = *(unsigned char *)w;
+	sum += answer;
+	}
+
+	sum = (sum >> 16) + (sum & 0xffff);
+	sum += (sum >> 16);
+	answer = ~sum;
+
+	return (answer);
+}
+
+
+/* Generate and send an ICMP HOST UNREACHABLE Packet */
+static void ipv4_host_unreach(struct tun_t *tun, void *pack, unsigned len) {
+
+	char send_buf[sizeof(struct ip) + sizeof(struct icmp) + len];
+	/* What are these 20 bytes on the end of pack? */
+	len = len - 20;
+	struct iphdr *iph = (struct iphdr *)pack;
+
+	memset(send_buf, 0, sizeof(send_buf));
+
+	struct ip *ip = (struct ip *)send_buf;
+	struct icmp *icmp = (struct icmp *)(ip + 1);
+
+	ip->ip_v = 4;
+	ip->ip_hl = 5;
+	ip->ip_tos = 0;
+	ip->ip_len = htons(sizeof(send_buf));
+	ip->ip_id = rand();
+	ip->ip_off = 0;
+	ip->ip_ttl = 64;
+	ip->ip_sum = 0;
+	ip->ip_p = IPPROTO_ICMP;
+	ip->ip_src.s_addr = iph->daddr;
+	//inet_pton(AF_INET, "10.20.0.4", &(ip->ip_src));
+	ip->ip_dst.s_addr = iph->saddr;
+
+	ip->ip_sum = inet_checksum(ip, sizeof(send_buf));
+
+	icmp->icmp_type = ICMP_DEST_UNREACH;
+	icmp->icmp_code = ICMP_HOST_UNREACH;
+	icmp->icmp_id = 0;
+	icmp->icmp_seq = 0;
+	icmp->icmp_cksum = 0;
+
+	memcpy(send_buf + sizeof(ip) + sizeof(icmp) + 12, pack, len);
+	icmp->icmp_cksum = inet_checksum(icmp, sizeof(icmp) + 12 + len);
+	tun_encaps(tun, send_buf, sizeof(send_buf));
+
+}
+
 /* Internet-originated IP packet, needs to be sent via GTP towards MS */
 static int cb_tun_ind(struct tun_t *tun, void *pack, unsigned len)
 {
@@ -622,18 +689,30 @@
 		return 0;
 
 	if (ippool_getip(pool, &ipm, &dst)) {
-		LOGTUN(LOGL_DEBUG, tun, "Received packet for APN(%s) with no PDP contex! (%s)\n",
-			apn->cfg.name,
+		LOGTUN(LOGL_DEBUG, tun, "Received packet from (%s) for APN(%s) but dest is not in pool!\n",
 			iph->version == 4 ?
 			inet_ntop(AF_INET, &iph->saddr, straddr, sizeof(straddr)) :
-			inet_ntop(AF_INET6, &ip6h->ip6_src, straddr, sizeof(straddr)));
+			inet_ntop(AF_INET6, &ip6h->ip6_src, straddr, sizeof(straddr)),
+			apn->cfg.name);
 		return 0;
 	}
-	LOGTUN(LOGL_DEBUG, tun, "Received packet for APN(%s)\n", apn->cfg.name);
+	LOGTUN(LOGL_DEBUG, tun, "Received packet for APN(%s), EUA(%s)\n", apn->cfg.name,
+			iph->version == 4 ?
+			inet_ntop(AF_INET, &iph->daddr, straddr, sizeof(straddr)) :
+			inet_ntop(AF_INET6, &ip6h->ip6_dst, straddr, sizeof(straddr)));
 
-	if (ipm->peer)		/* Check if a peer protocol is defined */
+	if (ipm->peer) {	/* Check if a peer protocol is defined */
 		gtp_data_req(apn->ggsn->gsn, (struct pdp_t *)ipm->peer, pack, len);
+		return 0;
+	}
+
+	if (iph->version != 4)
+		return 0;
+
+	LOGTUN(LOGL_DEBUG, tun, "No PDP Context for Destination Address, send host unreach.\n");
+	ipv4_host_unreach(tun, pack, len);
 	return 0;
+
 }
 
 /* MS-originated GTP1-U packet, needs to be sent via TUN device */