D-GSM 3/n: implement roaming by mslookup in osmo-hlr

Add mslookup client to find remote home HLRs of unknown IMSIs, and
proxy/forward GSUP for those to the right remote HLR instances.

Add remote_hlr.c to manage one GSUP client per remote HLR GSUP address.

Add proxy.c to keep state about remotely handled IMSIs (remote GSUP address,
MSISDN, and probably more in future patches).  The mslookup_server that
determines whether a given MSISDN is attached locally now also needs to look in
the proxy record: it is always the osmo-hlr immediately peering for the MSC
that should respond to mslookup service address queries like SIP and SMPP.
(Only gsup.hlr service is always answered by the home HLR.)

Add dgsm.c to set up an mdns mslookup client, ask for IMSI homes, and to decide
which GSUP is handled locally and which needs to go to a remote HLR.

Add full VTY config and VTY tests.

For a detailed overview of the D-GSM and mslookup related files, please see the
elaborate comment at the top of mslookup.c (already added in an earlier patch).

Change-Id: I2fe453553c90e6ee527ed13a13089900efd488aa
diff --git a/src/mslookup_server.c b/src/mslookup_server.c
index 9c4dc58..885adf8 100644
--- a/src/mslookup_server.c
+++ b/src/mslookup_server.c
@@ -27,6 +27,7 @@
 #include <osmocom/hlr/db.h>
 #include <osmocom/hlr/timestamp.h>
 #include <osmocom/hlr/mslookup_server.h>
+#include <osmocom/hlr/proxy.h>
 
 static const struct osmo_mslookup_result not_found = {
 		.rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
@@ -294,13 +295,84 @@
 	return true;
 }
 
-static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
-					uint32_t *lu_age_p,
-					struct osmo_ipa_name *local_msc_name)
+
+/* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop. Return
+ * true if it is attached at a local VLR, and we are serving as proxy for a remote home HLR.
+ */
+static bool subscriber_has_done_lu_here_proxy(const struct osmo_mslookup_query *query,
+					      uint32_t *lu_age,
+					      struct osmo_ipa_name *local_msc_name,
+					      struct proxy_subscr *ret_proxy_subscr)
+{
+	struct proxy_subscr proxy_subscr;
+	uint32_t age;
+	int rc;
+
+	/* See the local HLR record. If the subscriber is "at home" in this HLR and is also currently located here, we
+	 * will find a valid location updating and no vlr_via_proxy entry. */
+	switch (query->id.type) {
+	case OSMO_MSLOOKUP_ID_IMSI:
+		rc = proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, query->id.imsi);
+		break;
+	case OSMO_MSLOOKUP_ID_MSISDN:
+		rc = proxy_subscr_get_by_msisdn(&proxy_subscr, g_hlr->gs->proxy, query->id.msisdn);
+		break;
+	default:
+		LOGP(DDGSM, LOGL_ERROR, "%s: unknown ID type\n",
+		     osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
+		return false;
+	}
+
+	if (rc) {
+		LOGP(DDGSM, LOGL_DEBUG, "%s: does not exist in GSUP proxy\n",
+		     osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
+		return false;
+	}
+
+	/* We only need to care about CS LU, since only CS services need D-GSM routing. */
+	if (!timestamp_age(&proxy_subscr.cs.last_lu, &age)
+	    || age > g_hlr->mslookup.server.local_attach_max_age) {
+		LOGP(DDGSM, LOGL_ERROR,
+		     "%s: last attach was at local VLR (proxying for remote HLR), but too long ago: %us > %us\n",
+		     osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
+		     age, g_hlr->mslookup.server.local_attach_max_age);
+		return false;
+	}
+
+	if (proxy_subscr.cs.vlr_via_proxy.len) {
+		LOGP(DDGSM, LOGL_DEBUG, "%s: last attach is not at local VLR, but at VLR '%s' via proxy '%s'\n",
+		     osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
+		     osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_name),
+		     osmo_ipa_name_to_str(&proxy_subscr.cs.vlr_via_proxy));
+		return false;
+	}
+
+	*lu_age = age;
+	*local_msc_name = proxy_subscr.cs.vlr_name;
+	LOGP(DDGSM, LOGL_DEBUG, "%s: attached %u seconds ago at local VLR %s; proxying for remote HLR "
+	     OSMO_SOCKADDR_STR_FMT "\n",
+	     osmo_mslookup_result_name_c(OTC_SELECT, query, NULL),
+	     age, osmo_ipa_name_to_str(local_msc_name),
+	     OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr.remote_hlr_addr));
+
+	if (ret_proxy_subscr)
+		*ret_proxy_subscr = proxy_subscr;
+	return true;
+}
+
+bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
+				 uint32_t *lu_age_p, struct osmo_ipa_name *local_msc_name,
+				 char *ret_imsi, size_t ret_imsi_len)
 {
 	bool attached_here;
 	uint32_t lu_age = 0;
 	struct osmo_ipa_name msc_name = {};
+	bool attached_here_proxy;
+	uint32_t proxy_lu_age = 0;
+	struct osmo_ipa_name proxy_msc_name = {};
+	struct proxy_subscr proxy_subscr;
+	struct hlr_subscriber db_subscr;
+
 
 	/* First ask the local HLR db, but if the local proxy record indicates a more recent LU, use that instead.
 	 * For all usual cases, only one of these will reflect a LU, even if a subscriber had more than one home HLR:
@@ -310,9 +382,20 @@
 	 * the local HLR database, there might occur a situation where both reflect a LU. So, to be safe against all
 	 * situations, compare the two entries.
 	 */
-	attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, NULL);
+	attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name, &db_subscr);
+	attached_here_proxy = subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name, &proxy_subscr);
 
-	/* Future: If proxy has a younger lu, replace. */
+	/* If proxy has a younger lu, replace. */
+	if (attached_here_proxy && (!attached_here || (proxy_lu_age < lu_age))) {
+		attached_here = true;
+		lu_age = proxy_lu_age;
+		msc_name = proxy_msc_name;
+		if (ret_imsi)
+			osmo_strlcpy(ret_imsi, proxy_subscr.imsi, ret_imsi_len);
+	} else if (attached_here) {
+		if (ret_imsi)
+			osmo_strlcpy(ret_imsi, db_subscr.imsi, ret_imsi_len);
+	}
 
 	if (attached_here && !msc_name.len) {
 		LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n",
@@ -349,7 +432,7 @@
 	/* All other service types: answer when the subscriber has done a LU that is either listed in the local HLR or
 	 * in the GSUP proxy database: i.e. if the subscriber has done a Location Updating at an VLR belonging to this
 	 * HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
-	if (!subscriber_has_done_lu_here(query, &age, &msc_name)) {
+	if (!subscriber_has_done_lu_here(query, &age, &msc_name, NULL, 0)) {
 		*result = not_found;
 		return;
 	}