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/dgsm_vty.c b/src/dgsm_vty.c
index facd2b7..6f29d3b 100644
--- a/src/dgsm_vty.c
+++ b/src/dgsm_vty.c
@@ -61,6 +61,26 @@
 	return CMD_SUCCESS;
 }
 
+static int mslookup_client_mdns_to(struct vty *vty, int argc, const char **argv)
+{
+	const char *ip_str = argc > 0? argv[0] : g_hlr->mslookup.client.mdns.query_addr.ip;
+	const char *port_str = argc > 1? argv[1] : NULL;
+	uint16_t port_nr = port_str ? atoi(port_str) : g_hlr->mslookup.client.mdns.query_addr.port;
+	struct osmo_sockaddr_str addr;
+	if (osmo_sockaddr_str_from_str(&addr, ip_str, port_nr)
+	    || !osmo_sockaddr_str_is_nonzero(&addr)) {
+		vty_out(vty, "%% mslookup client: Invalid mDNS target address: %s %u%s",
+			ip_str, port_nr, VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	g_hlr->mslookup.client.mdns.query_addr = addr;
+	g_hlr->mslookup.client.mdns.enable = true;
+	g_hlr->mslookup.client.enable = true;
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
 #define MDNS_STR "Multicast DNS related configuration\n"
 #define MDNS_IP46_STR "multicast IPv4 address like " OSMO_MSLOOKUP_MDNS_IP4 \
 			" or IPv6 address like " OSMO_MSLOOKUP_MDNS_IP6 "\n"
@@ -71,6 +91,44 @@
 #define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
 #define PORT_STR "Service-specific port number\n"
 
+DEFUN(cfg_mslookup_mdns,
+      cfg_mslookup_mdns_cmd,
+      "mdns bind [IP] [<1-65535>]",
+      MDNS_STR
+      "Convenience shortcut: enable and configure both server and client for mDNS mslookup\n"
+      MDNS_IP46_STR MDNS_PORT_STR)
+{
+	int rc1 = mslookup_server_mdns_bind(vty, argc, argv);
+	int rc2 = mslookup_client_mdns_to(vty, argc, argv);
+	if (rc1 != CMD_SUCCESS)
+		return rc1;
+	return rc2;
+}
+
+DEFUN(cfg_mslookup_mdns_domain_suffix,
+      cfg_mslookup_mdns_domain_suffix_cmd,
+      "mdns domain-suffix DOMAIN_SUFFIX",
+      MDNS_STR MDNS_DOMAIN_SUFFIX_STR MDNS_DOMAIN_SUFFIX_STR)
+{
+	osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.server.mdns.domain_suffix, argv[0]);
+	osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.client.mdns.domain_suffix, argv[0]);
+	mslookup_server_mdns_config_apply();
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_no_mdns,
+      cfg_mslookup_no_mdns_cmd,
+      "no mdns bind",
+      NO_STR "Disable both server and client for mDNS mslookup\n")
+{
+	g_hlr->mslookup.server.mdns.enable = false;
+	g_hlr->mslookup.client.mdns.enable = false;
+	mslookup_server_mdns_config_apply();
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
 struct cmd_node mslookup_server_node = {
 	MSLOOKUP_SERVER_NODE,
 	"%s(config-mslookup-server)# ",
@@ -265,6 +323,81 @@
 	return CMD_SUCCESS;
 }
 
+struct cmd_node mslookup_client_node = {
+	MSLOOKUP_CLIENT_NODE,
+	"%s(config-mslookup-client)# ",
+	1,
+};
+
+DEFUN(cfg_mslookup_client,
+      cfg_mslookup_client_cmd,
+      "client",
+      "Enable and configure Distributed GSM mslookup client")
+{
+	vty->node = MSLOOKUP_CLIENT_NODE;
+	g_hlr->mslookup.client.enable = true;
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_no_client,
+      cfg_mslookup_no_client_cmd,
+      "no client",
+      NO_STR "Disable Distributed GSM mslookup client")
+{
+	g_hlr->mslookup.client.enable = false;
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_client_timeout,
+      cfg_mslookup_client_timeout_cmd,
+      "timeout <1-100000>",
+      "How long should the mslookup client wait for remote responses before evaluating received results\n"
+      "timeout in milliseconds\n")
+{
+	uint32_t val = atol(argv[0]);
+	g_hlr->mslookup.client.result_timeout_milliseconds = val;
+	return CMD_SUCCESS;
+}
+
+#define EXIT_HINT() \
+	if (vty->type != VTY_FILE) \
+		vty_out(vty, "%% 'exit' this node to apply changes%s", VTY_NEWLINE)
+
+
+DEFUN(cfg_mslookup_client_mdns_bind,
+      cfg_mslookup_client_mdns_bind_cmd,
+      "mdns bind [IP] [<1-65535>]",
+      MDNS_STR
+      "Enable mDNS client, and configure multicast address to send mDNS mslookup requests to\n"
+      MDNS_IP46_STR MDNS_PORT_STR)
+{
+	return mslookup_client_mdns_to(vty, argc, argv);
+}
+
+DEFUN(cfg_mslookup_client_mdns_domain_suffix,
+      cfg_mslookup_client_mdns_domain_suffix_cmd,
+      "mdns domain-suffix DOMAIN_SUFFIX",
+      MDNS_STR
+      MDNS_DOMAIN_SUFFIX_STR
+      MDNS_DOMAIN_SUFFIX_STR)
+{
+	osmo_talloc_replace_string(g_hlr, &g_hlr->mslookup.client.mdns.domain_suffix, argv[0]);
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_client_no_mdns_bind,
+      cfg_mslookup_client_no_mdns_bind_cmd,
+      "no mdns bind",
+      NO_STR "Disable mDNS client, do not query remote services by mDNS\n")
+{
+	g_hlr->mslookup.client.mdns.enable = false;
+	dgsm_mdns_client_config_apply();
+	return CMD_SUCCESS;
+}
+
 void config_write_msc_services(struct vty *vty, const char *indent, struct mslookup_server_msc_cfg *msc)
 {
 	struct mslookup_service_host *e;
@@ -282,7 +415,8 @@
 int config_write_mslookup(struct vty *vty)
 {
 	if (!g_hlr->mslookup.server.enable
-	    && llist_empty(&g_hlr->mslookup.server.local_site_services))
+	    && llist_empty(&g_hlr->mslookup.server.local_site_services)
+	    && !g_hlr->mslookup.client.enable)
 		return CMD_SUCCESS;
 
 	vty_out(vty, "mslookup%s", VTY_NEWLINE);
@@ -322,6 +456,57 @@
 			vty_out(vty, " no server%s", VTY_NEWLINE);
 	}
 
+	if (g_hlr->mslookup.client.enable) {
+		vty_out(vty, " client%s", VTY_NEWLINE);
+
+		if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
+			vty_out(vty, "  gateway-proxy %s %u%s",
+				g_hlr->mslookup.client.gsup_gateway_proxy.ip,
+				g_hlr->mslookup.client.gsup_gateway_proxy.port,
+				VTY_NEWLINE);
+
+		if (g_hlr->mslookup.client.mdns.enable
+		    && osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.mdns.query_addr))
+			vty_out(vty, "  mdns bind %s %u%s",
+				g_hlr->mslookup.client.mdns.query_addr.ip,
+				g_hlr->mslookup.client.mdns.query_addr.port,
+				VTY_NEWLINE);
+		if (strcmp(g_hlr->mslookup.client.mdns.domain_suffix, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT))
+			vty_out(vty, "  mdns domain-suffix %s%s",
+				g_hlr->mslookup.client.mdns.domain_suffix,
+				VTY_NEWLINE);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_client_gateway_proxy,
+      cfg_mslookup_client_gateway_proxy_cmd,
+      "gateway-proxy IP [<1-65535>]",
+      "Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI\n"
+      "IP address of the remote HLR\n" "GSUP port number (omit for default " OSMO_STRINGIFY_VAL(OSMO_GSUP_PORT) ")\n")
+{
+	const char *ip_str = argv[0];
+	const char *port_str = argc > 1 ? argv[1] : NULL;
+	struct osmo_sockaddr_str addr;
+
+	if (osmo_sockaddr_str_from_str(&addr, ip_str, port_str ? atoi(port_str) : OSMO_GSUP_PORT)
+	    || !osmo_sockaddr_str_is_nonzero(&addr)) {
+		vty_out(vty, "%% mslookup client: Invalid address for gateway-proxy: %s %s%s",
+			ip_str, port_str ? : "", VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	g_hlr->mslookup.client.gsup_gateway_proxy = addr;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_client_no_gateway_proxy,
+      cfg_mslookup_client_no_gateway_proxy_cmd,
+      "no gateway-proxy",
+      NO_STR "Disable gateway proxy for GSUP with unknown IMSIs\n")
+{
+	g_hlr->mslookup.client.gsup_gateway_proxy = (struct osmo_sockaddr_str){};
 	return CMD_SUCCESS;
 }
 
@@ -361,6 +546,9 @@
 	install_element(CONFIG_NODE, &cfg_mslookup_cmd);
 
 	install_node(&mslookup_node, config_write_mslookup);
+	install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_cmd);
+	install_element(MSLOOKUP_NODE, &cfg_mslookup_mdns_domain_suffix_cmd);
+	install_element(MSLOOKUP_NODE, &cfg_mslookup_no_mdns_cmd);
 	install_element(MSLOOKUP_NODE, &cfg_mslookup_server_cmd);
 	install_element(MSLOOKUP_NODE, &cfg_mslookup_no_server_cmd);
 
@@ -378,5 +566,15 @@
 	install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_cmd);
 	install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
 
+	install_element(MSLOOKUP_NODE, &cfg_mslookup_client_cmd);
+	install_element(MSLOOKUP_NODE, &cfg_mslookup_no_client_cmd);
+	install_node(&mslookup_client_node, NULL);
+	install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_timeout_cmd);
+	install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_bind_cmd);
+	install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_domain_suffix_cmd);
+	install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_bind_cmd);
+	install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_gateway_proxy_cmd);
+	install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_gateway_proxy_cmd);
+
 	install_element_ve(&do_mslookup_show_services_cmd);
 }