gtphub: add first bits of GRX ares

For the resolving function, change the function signature to return a
gtphub_peer_port. In consequence, publish two functions concerned with
gtphub_peer_port instances for use in test and gtphub_ext.c.

Add GGSN resolution queue, callback and cache. Simple implementation: if an
SGSN asks for a GGSN, it will first get no answer, and I hope it will ask again
once the GGSN is in the cache.

Within gtphub_ext.c, have a dummy sgsn struct, as the sgsn_ares code currently
depends on it (half the functions pass an sgsn instance pointer around, but the
other half use the global one).

In the unit tests, wrap away the ares initialization so that they can work
without a DNS server around. The netcat test breaks because of this, will
remove it.

Using sgsn_ares, implement the gtphub_resolve_ggsn_addr() function, I hope:
untested.

Minor cosmetics just to see if you're paying attention... ;)

Sponsored-by: On-Waves ehi
diff --git a/openbsc/src/gprs/gtphub.c b/openbsc/src/gprs/gtphub.c
index 440f00f..eae4cf2 100644
--- a/openbsc/src/gprs/gtphub.c
+++ b/openbsc/src/gprs/gtphub.c
@@ -33,11 +33,13 @@
 
 #include <openbsc/gtphub.h>
 #include <openbsc/debug.h>
+#include <openbsc/gprs_utils.h>
 
 #include <osmocom/core/utils.h>
 #include <osmocom/core/logging.h>
 #include <osmocom/core/socket.h>
 
+
 #define GTPHUB_DEBUG 1
 
 static const int GTPH_GC_TICK_SECONDS = 1;
@@ -345,13 +347,13 @@
  * The first IEI is reached by passing i = 0.
  * imsi must point at allocated space of (at least) 8 bytes.
  * Return 1 on success, or 0 if not found. */
-static int get_ie_imsi(union gtpie_member *ie[], uint8_t *imsi, int i)
+static int get_ie_imsi(union gtpie_member *ie[], int i, uint8_t *imsi)
 {
 	return gtpie_gettv0(ie, GTPIE_IMSI, i, imsi, 8) == 0;
 }
 
 /* Analogous to get_ie_imsi(). nsapi must point at a single uint8_t. */
-static int get_ie_nsapi(union gtpie_member *ie[], uint8_t *nsapi, int i)
+static int get_ie_nsapi(union gtpie_member *ie[], int i, uint8_t *nsapi)
 {
 	return gtpie_gettv1(ie, GTPIE_NSAPI, i, nsapi) == 0;
 }
@@ -379,6 +381,33 @@
 	return str;
 }
 
+static const char *get_ie_imsi_str(union gtpie_member *ie[], int i)
+{
+	uint8_t imsi_buf[8];
+	if (!get_ie_imsi(ie, i, imsi_buf))
+		return NULL;
+	return imsi_to_str(imsi_buf);
+}
+
+static const char *get_ie_apn_str(union gtpie_member *ie[])
+{
+	static char apn_buf[GSM_APN_LENGTH];
+	unsigned int len;
+	if (gtpie_gettlv(ie, GTPIE_APN, 0,
+			 &len, apn_buf, sizeof(apn_buf)) != 0)
+		return NULL;
+
+	if (!len)
+		return NULL;
+
+	if (len > (sizeof(apn_buf) - 1))
+		len = sizeof(apn_buf) - 1;
+	apn_buf[len] = '\0';
+
+	return  gprs_apn_to_str(apn_buf, (uint8_t*)apn_buf, len);
+}
+
+
 /* Validate header, and index information elements. Write decoded packet
  * information to *res. res->data will point at the given data buffer. On
  * error, p->rc is set <= 0 (see enum gtp_rc). */
@@ -416,15 +445,15 @@
 	int i;
 
 	for (i = 0; i < 10; i++) {
-		uint8_t imsi[8];
-		if (!get_ie_imsi(res->ie, imsi, i))
+		const char *imsi = get_ie_imsi_str(res->ie, i);
+		if (!imsi)
 			break;
-		LOG("| IMSI %s\n", imsi_to_str(imsi));
+		LOG("| IMSI %s\n", imsi);
 	}
 
 	for (i = 0; i < 10; i++) {
 		uint8_t nsapi;
-		if (!get_ie_nsapi(res->ie, &nsapi, i))
+		if (!get_ie_nsapi(res->ie, i, &nsapi))
 			break;
 		LOG("| NSAPI %d\n", (int)nsapi);
 	}
@@ -481,7 +510,6 @@
 			expiring_item_del(m);
 			expired ++;
 		} else {
-			LOG("Not expired: %d > %d\n", (int)m->expiry, (int)now);
 			/* The items are added sorted by expiry. So when we hit
 			 * an unexpired entry, only more unexpired ones will
 			 * follow. */
@@ -654,19 +682,15 @@
 						    struct gtp_packet_desc *p);
 
 /* See gtphub_ext.c (wrapped by unit test) */
-int gtphub_resolve_ggsn_addr(struct gtphub *hub,
-			     struct osmo_sockaddr *result,
-			     struct gtp_packet_desc *p);
+struct gtphub_peer_port *gtphub_resolve_ggsn_addr(struct gtphub *hub,
+						  const char *imsi_str,
+						  const char *apn_ni_str);
+int gtphub_ares_init(struct gtphub *hub);
 
 static struct gtphub_peer_port *gtphub_port_find(const struct gtphub_bind *bind,
 						 const struct gsn_addr *addr,
 						 uint16_t port);
 
-static struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub,
-						 struct gtphub_bind *bind,
-						 const struct gsn_addr *addr,
-						 uint16_t port);
-
 static void gtphub_zero(struct gtphub *hub)
 {
 	ZERO_STRUCT(hub);
@@ -833,7 +857,7 @@
 	return gtphub_peer_strb(peer, buf, sizeof(buf));
 }
 
-static const char *gtphub_port_str(struct gtphub_peer_port *port)
+const char *gtphub_port_str(struct gtphub_peer_port *port)
 {
 	static char buf[256];
 	return gtphub_port_strb(port, buf, sizeof(buf));
@@ -1562,6 +1586,49 @@
 	return received;
 }
 
+static void resolved_gssn_del_cb(struct expiring_item *expi)
+{
+	struct gtphub_resolved_ggsn *ggsn;
+	ggsn = container_of(expi, struct gtphub_resolved_ggsn, expiry_entry);
+
+	gtphub_port_ref_count_dec(ggsn->peer);
+	llist_del(&ggsn->entry);
+
+	ggsn->expiry_entry.del_cb = 0;
+	expiring_item_del(&ggsn->expiry_entry);
+
+	talloc_free(ggsn);
+}
+
+void gtphub_resolved_ggsn(struct gtphub *hub, const char *apn_oi_str,
+			  struct gsn_addr *resolved_addr,
+			  time_t now)
+{
+	struct gtphub_peer_port *pp;
+	struct gtphub_resolved_ggsn *ggsn;
+
+	pp = gtphub_port_have(hub, &hub->to_ggsns[GTPH_PLANE_CTRL],
+			      resolved_addr, 2123);
+	if (!pp) {
+		LOGERR("Internal: Cannot create/find peer '%s'\n",
+		       gsn_addr_to_str(resolved_addr));
+		return;
+	}
+
+	ggsn = talloc_zero(osmo_gtphub_ctx, struct gtphub_resolved_ggsn);
+	OSMO_ASSERT(ggsn);
+
+	ggsn->peer = pp;
+	gtphub_port_ref_count_inc(pp);
+
+	strncpy(ggsn->apn_oi_str, apn_oi_str, sizeof(ggsn->apn_oi_str));
+
+	ggsn->expiry_entry.del_cb = resolved_gssn_del_cb;
+	expiry_add(&hub->expire_tei_maps, &ggsn->expiry_entry, now);
+
+	llist_add(&ggsn->entry, &hub->resolved_ggsns);
+}
+
 static int gtphub_gc_peer_port(struct gtphub_peer_port *pp)
 {
 	return pp->ref_count == 0;
@@ -1654,6 +1721,8 @@
 {
 	gtphub_zero(hub);
 
+	INIT_LLIST_HEAD(&hub->resolved_ggsns);
+
 	expiry_init(&hub->expire_seq_maps, GTPH_SEQ_MAPPING_EXPIRY_SECS);
 	expiry_init(&hub->expire_tei_maps, GTPH_TEI_MAPPING_EXPIRY_MINUTES * 60);
 
@@ -1693,6 +1762,7 @@
 	int rc;
 
 	gtphub_init(hub);
+	gtphub_ares_init(hub);
 
 	int plane_idx;
 	for (plane_idx = 0; plane_idx < GTPH_PLANE_N; plane_idx++) {
@@ -1882,10 +1952,10 @@
 	return pp;
 }
 
-static struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub,
-						 struct gtphub_bind *bind,
-						 const struct gsn_addr *addr,
-						 uint16_t port)
+struct gtphub_peer_port *gtphub_port_have(struct gtphub *hub,
+					  struct gtphub_bind *bind,
+					  const struct gsn_addr *addr,
+					  uint16_t port)
 {
 	struct gtphub_peer_addr *a = gtphub_addr_have(hub, bind, addr);
 
@@ -1896,47 +1966,12 @@
 	return gtphub_addr_add_port(a, port);
 }
 
-/* port_override: <=0: use port from addr; >0: use this number as port. Return
- * NULL if the address cannot be parsed. */
-static struct gtphub_peer_port *gtphub_port_have_sockaddr(struct gtphub *hub,
-							  struct gtphub_bind *bind,
-							  const struct osmo_sockaddr *addr,
-							  int port_override)
-{
-	struct gsn_addr gsna;
-	uint16_t port;
-	if (gsn_addr_from_sockaddr(&gsna, &port, addr) != 0)
-		return NULL;
-
-	if (port_override > 0)
-		port = port_override;
-	return gtphub_port_have(hub, bind, &gsna, port);
-}
-
-static struct gtphub_peer_port *gtphub_have_ggsn(struct gtphub *hub,
-						 struct osmo_sockaddr *addr,
-						 unsigned int plane_idx,
-						 int port_override)
-{
-	if (port_override == 0)
-		port_override = gtphub_plane_idx_default_port[plane_idx];
-
-	return gtphub_port_have_sockaddr(hub, &hub->to_ggsns[plane_idx], addr,
-					 port_override);
-}
-
 static struct gtphub_peer_port *gtphub_resolve_ggsn(struct gtphub *hub,
 						    struct gtp_packet_desc *p)
 {
-	int rc;
-
-	struct osmo_sockaddr addr;
-
-	rc = gtphub_resolve_ggsn_addr(hub, &addr, p);
-	if (rc < 0)
-		return NULL;
-
-	return gtphub_have_ggsn(hub, &addr, p->plane_idx, -1);
+	return gtphub_resolve_ggsn_addr(hub,
+					get_ie_imsi_str(p->ie, 0),
+					get_ie_apn_str(p->ie));
 }