ippool: Implement and use blacklist instead of blindly using IPPOOL_NOGATEWAY

Commit dda21ed7d4a897c9284c69175d0da598598eae40 modified previous calls
to ippool_new() removing the pass of flags to avoid allocating certain
problematic IPs from the pool to MS, such as the network, gateway and
broadcast IPs.

Today I did some unsucessful tests with osmo-ggsn with a pool "ip prefix
dynamic 176.16.222.0/24", and thus IP 176.16.222.0 was being assigned to
the MS. De-capsulated DNS packets were received in the tun interface,
but the Linux system in there was unable to correctly forward the
packets to the gateway interface connected to the Internet. However,
adding a second MS which got 176.16.222.1 had its packets forwarded
correctly.

However, previous implementation relies on flag IPPOOL_NOGATEWAY flag to
blindly blacklist first IP after the network ip (ie, .0 and .1 are
removed), which limits the IP reserved for the tun device to be .1. If a
different IP in the range is assigned, it may cause issues. As a result,
a blacklist is introduced in this commit to dynamically fetch the tun IP
address and exlucde it from the pool of available IPs.

Change-Id: I8e91f7280d60490c858a769dd578c1c8e54e9243
diff --git a/ggsn/ggsn.c b/ggsn/ggsn.c
index ff760cf..8b576ab 100644
--- a/ggsn/ggsn.c
+++ b/ggsn/ggsn.c
@@ -146,10 +146,50 @@
 	return 0;
 }
 
+
+static int alloc_ippool_blacklist(struct apn_ctx *apn, const struct tun_t *tun, struct in46_prefix **blacklist, bool ipv6)
+{
+
+	int flags, len, len2, i;
+
+	if (ipv6)
+		flags = IP_TYPE_IPv6_NONLINK;
+	else
+		flags = IP_TYPE_IPv4;
+
+	while (1) {
+		len = tun_ip_local_get(apn->tun.tun, NULL, 0, flags);
+		if (len < 1)
+			return len;
+
+		*blacklist = talloc_zero_size(apn, len * sizeof(struct in46_prefix));
+		len2 = tun_ip_local_get(apn->tun.tun, *blacklist, len, flags);
+		if (len2 < 1) {
+			talloc_free(*blacklist);
+			return len2;
+		}
+
+		if (len2 > len) /* iface was added between 2 calls, repeat operation */
+			talloc_free(*blacklist);
+		else
+			break;
+	}
+
+	for (i = 0; i < len2; i++)
+		LOGPAPN(LOGL_INFO, apn, "Blacklist tun IP %s\n",
+			in46p_ntoa(&(*blacklist)[i]));
+
+	return len2;
+}
+
 /* actually start the APN with its current config */
 int apn_start(struct apn_ctx *apn)
 {
+	int ippool_flags = IPPOOL_NONETWORK | IPPOOL_NOBROADCAST;
 	struct in46_prefix ipv6_tun_linklocal_ip;
+	struct in46_prefix *blacklist;
+	int blacklist_size;
+
 	if (apn->started)
 		return 0;
 
@@ -231,24 +271,34 @@
 	if (apn->v4.cfg.dynamic_prefix.addr.len) {
 		LOGPAPN(LOGL_INFO, apn, "Creating IPv4 pool %s\n",
 			in46p_ntoa(&apn->v4.cfg.dynamic_prefix));
+		if ((blacklist_size = alloc_ippool_blacklist(apn, apn->tun.tun, &blacklist, false)) < 0)
+			LOGPAPN(LOGL_ERROR, apn, "Failed obtaining IPv4 tun IPs\n");
 		if (ippool_new(&apn->v4.pool, &apn->v4.cfg.dynamic_prefix,
-				&apn->v4.cfg.static_prefix, 0)) {
+				&apn->v4.cfg.static_prefix, ippool_flags,
+				blacklist, blacklist_size)) {
 			LOGPAPN(LOGL_ERROR, apn, "Failed to create IPv4 pool\n");
+			talloc_free(blacklist);
 			apn_stop(apn, false);
 			return -1;
 		}
+		talloc_free(blacklist);
 	}
 
 	/* Create IPv6 pool */
 	if (apn->v6.cfg.dynamic_prefix.addr.len) {
 		LOGPAPN(LOGL_INFO, apn, "Creating IPv6 pool %s\n",
 			in46p_ntoa(&apn->v6.cfg.dynamic_prefix));
+		if ((blacklist_size = alloc_ippool_blacklist(apn, apn->tun.tun, &blacklist, true)) < 0)
+			LOGPAPN(LOGL_ERROR, apn, "Failed obtaining IPv6 tun IPs\n");
 		if (ippool_new(&apn->v6.pool, &apn->v6.cfg.dynamic_prefix,
-				&apn->v6.cfg.static_prefix, 0)) {
+				&apn->v6.cfg.static_prefix, ippool_flags,
+				blacklist, blacklist_size)) {
 			LOGPAPN(LOGL_ERROR, apn, "Failed to create IPv6 pool\n");
+			talloc_free(blacklist);
 			apn_stop(apn, false);
 			return -1;
 		}
+		talloc_free(blacklist);
 	}
 
 	LOGPAPN(LOGL_NOTICE, apn, "Successfully started\n");