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/Makefile.am b/src/Makefile.am
index 093db8e..94575ad 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -53,6 +53,9 @@
hlr_vty_subscr.c \
gsup_send.c \
hlr_ussd.c \
+ proxy.c \
+ dgsm.c \
+ remote_hlr.c \
lu_fsm.c \
timestamp.c \
mslookup_server.c \
diff --git a/src/dgsm.c b/src/dgsm.c
new file mode 100644
index 0000000..57012c8
--- /dev/null
+++ b/src/dgsm.c
@@ -0,0 +1,247 @@
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/mslookup/mslookup_client.h>
+#include <osmocom/mslookup/mslookup_client_mdns.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include <osmocom/gsupclient/gsup_peer_id.h>
+#include <osmocom/hlr/logging.h>
+#include <osmocom/hlr/hlr.h>
+#include <osmocom/hlr/db.h>
+#include <osmocom/hlr/gsup_router.h>
+#include <osmocom/hlr/gsup_server.h>
+#include <osmocom/hlr/dgsm.h>
+#include <osmocom/hlr/proxy.h>
+#include <osmocom/hlr/remote_hlr.h>
+#include <osmocom/hlr/mslookup_server.h>
+#include <osmocom/hlr/mslookup_server_mdns.h>
+#include <osmocom/hlr/dgsm.h>
+
+void *dgsm_ctx = NULL;
+
+static void resolve_hlr_result_cb(struct osmo_mslookup_client *client,
+ uint32_t request_handle,
+ const struct osmo_mslookup_query *query,
+ const struct osmo_mslookup_result *result)
+{
+ struct proxy *proxy = g_hlr->gs->proxy;
+ struct proxy_subscr proxy_subscr;
+ const struct osmo_sockaddr_str *remote_hlr_addr;
+
+ /* A remote HLR is answering back, indicating that it is the home HLR for a given IMSI.
+ * There should be a mostly empty proxy entry for that IMSI.
+ * Add the remote address data in the proxy. */
+ if (query->id.type != OSMO_MSLOOKUP_ID_IMSI) {
+ LOGP(DDGSM, LOGL_ERROR, "Expected IMSI ID type in mslookup query+result: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, result));
+ return;
+ }
+
+ if (result->rc != OSMO_MSLOOKUP_RC_RESULT) {
+ LOG_DGSM(query->id.imsi, LOGL_ERROR, "Failed to resolve remote HLR: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, result));
+ proxy_subscr_del(proxy, query->id.imsi);
+ return;
+ }
+
+ if (osmo_sockaddr_str_is_nonzero(&result->host_v4))
+ remote_hlr_addr = &result->host_v4;
+ else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
+ remote_hlr_addr = &result->host_v6;
+ else {
+ LOG_DGSM(query->id.imsi, LOGL_ERROR, "Invalid address for remote HLR: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, result));
+ proxy_subscr_del(proxy, query->id.imsi);
+ return;
+ }
+
+ if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, query->id.imsi)) {
+ LOG_DGSM(query->id.imsi, LOGL_ERROR, "No proxy entry for mslookup result: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, result));
+ return;
+ }
+
+ proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, remote_hlr_addr);
+}
+
+/* Return true when the message has been handled by D-GSM. */
+bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req)
+{
+ struct proxy_subscr proxy_subscr;
+ struct proxy *proxy = g_hlr->gs->proxy;
+ struct osmo_mslookup_query query;
+ struct osmo_mslookup_query_handling handling;
+ uint32_t request_handle;
+
+ /* If the IMSI is known in the local HLR, then we won't proxy. */
+ if (db_subscr_exists_by_imsi(g_hlr->dbc, req->gsup.imsi) == 0)
+ return false;
+
+ /* Are we already forwarding this IMSI to a remote HLR? */
+ if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi) == 0) {
+ proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
+ return true;
+ }
+
+ /* The IMSI is not known locally, so we want to proxy to a remote HLR, but no proxy entry exists yet. We need to
+ * look up the subscriber in remote HLRs via D-GSM mslookup, forward GSUP and reply once a result is back from
+ * there. Defer message and kick off MS lookup. */
+
+ /* Add a proxy entry without a remote address to indicate that we are busy querying for a remote HLR. */
+ proxy_subscr = (struct proxy_subscr){};
+ OSMO_STRLCPY_ARRAY(proxy_subscr.imsi, req->gsup.imsi);
+ if (proxy_subscr_create_or_update(proxy, &proxy_subscr)) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to create proxy entry\n");
+ return true;
+ }
+
+ /* Is a fixed gateway proxy configured? */
+ if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
+ proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, &g_hlr->mslookup.client.gsup_gateway_proxy);
+
+ /* Proxy database modified, update info */
+ if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi)) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Internal proxy error\n");
+ return true;
+ }
+
+ proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
+ return true;
+ }
+
+ /* Kick off an mslookup for the remote HLR? This check could be up first on the top, but do it only now so that
+ * if the mslookup client disconnected, we still continue to service open proxy entries. */
+ if (!osmo_mslookup_client_active(g_hlr->mslookup.client.client)) {
+ LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
+ return false;
+ }
+
+ /* First spool message, then kick off mslookup. If the proxy denies this message type, then don't do anything. */
+ if (proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req)) {
+ /* If the proxy denied forwarding, an error response was already generated. */
+ return true;
+ }
+
+ query = (struct osmo_mslookup_query){
+ .id = {
+ .type = OSMO_MSLOOKUP_ID_IMSI,
+ },
+ };
+ OSMO_STRLCPY_ARRAY(query.id.imsi, req->gsup.imsi);
+ OSMO_STRLCPY_ARRAY(query.service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
+ handling = (struct osmo_mslookup_query_handling){
+ .min_wait_milliseconds = g_hlr->mslookup.client.result_timeout_milliseconds,
+ .result_cb = resolve_hlr_result_cb,
+ };
+ request_handle = osmo_mslookup_client_request(g_hlr->mslookup.client.client, &query, &handling);
+ if (!request_handle) {
+ LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Error dispatching mslookup query for home HLR: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, &query, NULL));
+ proxy_subscr_del(proxy, req->gsup.imsi);
+ /* mslookup seems to not be working. Try handling it locally. */
+ return false;
+ }
+
+ return true;
+}
+
+void dgsm_init(void *ctx)
+{
+ dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
+ INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
+
+ g_hlr->mslookup.server.local_attach_max_age = 60 * 60;
+
+ g_hlr->mslookup.client.result_timeout_milliseconds = 2000;
+
+ g_hlr->gsup_unit_name.unit_name = "HLR";
+ g_hlr->gsup_unit_name.serno = "unnamed-HLR";
+ g_hlr->gsup_unit_name.swversion = PACKAGE_NAME "-" PACKAGE_VERSION;
+
+ osmo_sockaddr_str_from_str(&g_hlr->mslookup.server.mdns.bind_addr,
+ OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
+ osmo_sockaddr_str_from_str(&g_hlr->mslookup.client.mdns.query_addr,
+ OSMO_MSLOOKUP_MDNS_IP4, OSMO_MSLOOKUP_MDNS_PORT);
+}
+
+void dgsm_start(void *ctx)
+{
+ g_hlr->mslookup.client.client = osmo_mslookup_client_new(dgsm_ctx);
+ OSMO_ASSERT(g_hlr->mslookup.client.client);
+ g_hlr->mslookup.allow_startup = true;
+ mslookup_server_mdns_config_apply();
+ dgsm_mdns_client_config_apply();
+}
+
+void dgsm_stop()
+{
+ g_hlr->mslookup.allow_startup = false;
+ mslookup_server_mdns_config_apply();
+ dgsm_mdns_client_config_apply();
+}
+
+void dgsm_mdns_client_config_apply(void)
+{
+ /* Check whether to start/stop/restart mDNS client */
+ const struct osmo_sockaddr_str *current_bind_addr;
+ const char *current_domain_suffix;
+ current_bind_addr = osmo_mslookup_client_method_mdns_get_bind_addr(g_hlr->mslookup.client.mdns.running);
+ current_domain_suffix = osmo_mslookup_client_method_mdns_get_domain_suffix(g_hlr->mslookup.client.mdns.running);
+
+ bool should_run = g_hlr->mslookup.allow_startup
+ && g_hlr->mslookup.client.enable && g_hlr->mslookup.client.mdns.enable;
+
+ bool should_stop = g_hlr->mslookup.client.mdns.running &&
+ (!should_run
+ || osmo_sockaddr_str_cmp(&g_hlr->mslookup.client.mdns.query_addr,
+ current_bind_addr)
+ || strcmp(g_hlr->mslookup.client.mdns.domain_suffix,
+ current_domain_suffix));
+
+ if (should_stop) {
+ osmo_mslookup_client_method_del(g_hlr->mslookup.client.client, g_hlr->mslookup.client.mdns.running);
+ g_hlr->mslookup.client.mdns.running = NULL;
+ LOGP(DDGSM, LOGL_NOTICE, "Stopped mslookup mDNS client\n");
+ }
+
+ if (should_run && !g_hlr->mslookup.client.mdns.running) {
+ g_hlr->mslookup.client.mdns.running =
+ osmo_mslookup_client_add_mdns(g_hlr->mslookup.client.client,
+ g_hlr->mslookup.client.mdns.query_addr.ip,
+ g_hlr->mslookup.client.mdns.query_addr.port,
+ -1,
+ g_hlr->mslookup.client.mdns.domain_suffix);
+ if (!g_hlr->mslookup.client.mdns.running)
+ LOGP(DDGSM, LOGL_ERROR, "Failed to start mslookup mDNS client with target "
+ OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
+ else
+ LOGP(DDGSM, LOGL_NOTICE, "Started mslookup mDNS client, sending mDNS requests to multicast "
+ OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.mdns.query_addr));
+ }
+
+ if (g_hlr->mslookup.client.enable && osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy))
+ LOGP(DDGSM, LOGL_NOTICE,
+ "mslookup client: all GSUP requests for unknown IMSIs will be forwarded to"
+ " gateway-proxy " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.gsup_gateway_proxy));
+}
diff --git a/src/dgsm_vty.c b/src/dgsm_vty.c
index 83c6820..277fc54 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);
}
diff --git a/src/gsup_server.c b/src/gsup_server.c
index effbdb0..35ad4e4 100644
--- a/src/gsup_server.c
+++ b/src/gsup_server.c
@@ -502,3 +502,39 @@
return 0;
}
+
+int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_gsup_peer_id *to_peer,
+ struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup)
+{
+ int rc;
+ /* To forward to a remote entity (HLR, SMSC,...), we need to indicate the original source name in the Source
+ * Name IE to make sure the reply can be routed back. Store the sender in gsup->source_name -- the remote entity
+ * is required to return this as gsup->destination_name so that the reply gets routed to the original sender. */
+ struct osmo_gsup_message forward = *(modified_gsup? : &req->gsup);
+
+ if (req->source_name.type != OSMO_GSUP_PEER_ID_IPA_NAME) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
+ osmo_gsup_peer_id_type_name(req->source_name.type));
+ rc = -ENOTSUP;
+ goto routing_error;
+ }
+ forward.source_name = req->source_name.ipa_name.val;
+ forward.source_name_len = req->source_name.ipa_name.len;
+
+ if (to_peer->type != OSMO_GSUP_PEER_ID_IPA_NAME) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
+ osmo_gsup_peer_id_type_name(to_peer->type));
+ rc = -ENOTSUP;
+ goto routing_error;
+ }
+ LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_gsup_peer_id_to_str(to_peer));
+ rc = osmo_gsup_enc_send_to_ipa_name(server, &to_peer->ipa_name, &forward);
+ if (rc)
+ goto routing_error;
+ osmo_gsup_req_free(req);
+ return 0;
+
+routing_error:
+ osmo_gsup_req_respond_msgt(req, OSMO_GSUP_MSGT_ROUTING_ERROR, true);
+ return rc;
+}
diff --git a/src/hlr.c b/src/hlr.c
index 70bd2a5..fee0920 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -36,6 +36,7 @@
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/mslookup/mslookup_client.h>
#include <osmocom/gsupclient/gsup_peer_id.h>
#include <osmocom/hlr/db.h>
@@ -47,6 +48,8 @@
#include <osmocom/hlr/rand.h>
#include <osmocom/hlr/hlr_vty.h>
#include <osmocom/hlr/hlr_ussd.h>
+#include <osmocom/hlr/dgsm.h>
+#include <osmocom/hlr/proxy.h>
#include <osmocom/hlr/lu_fsm.h>
#include <osmocom/mslookup/mdns.h>
@@ -500,6 +503,16 @@
}
}
+ /* Distributed GSM: check whether to proxy for / lookup a remote HLR.
+ * It would require less database hits to do this only if a local-only operation fails with "unknown IMSI", but
+ * it becomes semantically easier if we do this once-off ahead of time. */
+ if (osmo_mslookup_client_active(g_hlr->mslookup.client.client)
+ || osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
+ if (dgsm_check_forward_gsup_msg(req))
+ return 0;
+ }
+
+ /* HLR related messages that are handled at this HLR instance */
switch (req->gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
@@ -699,6 +712,7 @@
INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
g_hlr->db_file_path = talloc_strdup(g_hlr, HLR_DEFAULT_DB_FILE_PATH);
g_hlr->mslookup.server.mdns.domain_suffix = talloc_strdup(g_hlr, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT);
+ g_hlr->mslookup.client.mdns.domain_suffix = talloc_strdup(g_hlr, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT);
/* Init default (call independent) SS session guard timeout value */
g_hlr->ncss_guard_timeout = NCSS_GUARD_TIMEOUT_DEFAULT;
@@ -709,6 +723,9 @@
exit(1);
}
+ /* Set up llists and objects, startup is happening from VTY commands. */
+ dgsm_init(hlr_ctx);
+
osmo_stats_init(hlr_ctx);
vty_init(&vty_info);
ctrl_vty_init(hlr_ctx);
@@ -764,10 +781,13 @@
LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
exit(1);
}
+ proxy_init(g_hlr->gs);
g_hlr->ctrl_bind_addr = ctrl_vty_get_bind_addr();
g_hlr->ctrl = hlr_controlif_setup(g_hlr);
+ dgsm_start(hlr_ctx);
+
osmo_init_ignore_signals();
signal(SIGINT, &signal_hdlr);
signal(SIGTERM, &signal_hdlr);
@@ -784,6 +804,7 @@
while (!quit)
osmo_select_main_ctx(0);
+ dgsm_stop();
osmo_gsup_server_destroy(g_hlr->gs);
db_close(g_hlr->dbc);
diff --git a/src/hlr_vty.c b/src/hlr_vty.c
index 6701cd9..e959be8 100644
--- a/src/hlr_vty.c
+++ b/src/hlr_vty.c
@@ -146,6 +146,24 @@
return CMD_SUCCESS;
}
+DEFUN(cfg_hlr_gsup_ipa_name,
+ cfg_hlr_gsup_ipa_name_cmd,
+ "ipa-name NAME",
+ "Set the IPA name of this HLR, for proxying to remote HLRs\n"
+ "A globally unique name for this HLR. For example: PLMN + redundancy server number: HLR-901-70-0. "
+ "This name is used for GSUP routing and must be set if multiple HLRs interconnect (e.g. mslookup "
+ "for Distributed GSM).\n")
+{
+ if (vty->type != VTY_FILE) {
+ vty_out(vty, "gsup/ipa-name: The GSUP IPA name cannot be changed at run-time; "
+ "It can only be set in the configuraton file.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ g_hlr->gsup_unit_name.serno = talloc_strdup(g_hlr, argv[0]);
+ return CMD_SUCCESS;
+}
+
/***********************************************************************
* USSD Entity
***********************************************************************/
@@ -444,6 +462,7 @@
install_node(&gsup_node, config_write_hlr_gsup);
install_element(GSUP_NODE, &cfg_hlr_gsup_bind_ip_cmd);
+ install_element(GSUP_NODE, &cfg_hlr_gsup_ipa_name_cmd);
install_element(HLR_NODE, &cfg_database_cmd);
diff --git a/src/logging.c b/src/logging.c
index 15ef596..eab0510 100644
--- a/src/logging.c
+++ b/src/logging.c
@@ -37,6 +37,12 @@
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
+ [DDGSM] = {
+ .name = "DDGSM",
+ .description = "Distributed GSM: MS lookup and proxy",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
};
const struct log_info hlr_log_info = {
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;
}
diff --git a/src/proxy.c b/src/proxy.c
new file mode 100644
index 0000000..b9cd313
--- /dev/null
+++ b/src/proxy.c
@@ -0,0 +1,554 @@
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <talloc.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/gsm/gsm48_ie.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include <osmocom/gsupclient/gsup_req.h>
+
+#include <osmocom/hlr/logging.h>
+#include <osmocom/hlr/proxy.h>
+#include <osmocom/hlr/remote_hlr.h>
+#include <osmocom/hlr/gsup_server.h>
+#include <osmocom/hlr/gsup_router.h>
+
+#define LOG_PROXY_SUBSCR(proxy_subscr, level, fmt, args...) \
+ LOGP(DDGSM, level, "(Proxy IMSI-%s MSISDN-%s HLR-" OSMO_SOCKADDR_STR_FMT ") " fmt, \
+ ((proxy_subscr) && *(proxy_subscr)->imsi)? (proxy_subscr)->imsi : "?", \
+ ((proxy_subscr) && *(proxy_subscr)->msisdn)? (proxy_subscr)->msisdn : "?", \
+ OSMO_SOCKADDR_STR_FMT_ARGS((proxy_subscr)? &(proxy_subscr)->remote_hlr_addr : NULL), \
+ ##args)
+
+#define LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup_msg, level, fmt, args...) \
+ LOG_PROXY_SUBSCR(proxy_subscr, level, "%s: " fmt, \
+ (gsup_msg) ? osmo_gsup_message_type_name((gsup_msg)->message_type) : "NULL", \
+ ##args)
+
+/* The proxy subscriber database.
+ * Why have a separate struct to add an llist_head entry?
+ * This is to keep the option open to store the proxy data in the database instead, without any visible effect outside
+ * of proxy.c. */
+struct proxy_subscr_listentry {
+ struct llist_head entry;
+ timestamp_t last_update;
+ struct proxy_subscr data;
+};
+
+struct proxy_pending_gsup_req {
+ struct llist_head entry;
+ struct osmo_gsup_req *req;
+ timestamp_t received_at;
+};
+
+/* Defer a GSUP message until we know a remote HLR to proxy to.
+ * Where to send this GSUP message is indicated by its IMSI: as soon as an MS lookup has yielded the IMSI's home HLR,
+ * that's where the message should go. */
+static void proxy_deferred_gsup_req_add(struct proxy *proxy, struct osmo_gsup_req *req)
+{
+ struct proxy_pending_gsup_req *m;
+
+ m = talloc_zero(proxy, struct proxy_pending_gsup_req);
+ OSMO_ASSERT(m);
+ m->req = req;
+ timestamp_update(&m->received_at);
+ llist_add_tail(&m->entry, &proxy->pending_gsup_reqs);
+}
+
+static void proxy_pending_req_remote_hlr_connect_result(struct osmo_gsup_req *req, struct remote_hlr *remote_hlr)
+{
+ if (!remote_hlr || !remote_hlr_is_up(remote_hlr)) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_IMSI_UNKNOWN, "Proxy: Failed to connect to home HLR");
+ return;
+ }
+
+ remote_hlr_gsup_forward_to_remote_hlr(remote_hlr, req, NULL);
+}
+
+static bool proxy_deferred_gsup_req_waiting(struct proxy *proxy, const char *imsi)
+{
+ struct proxy_pending_gsup_req *p;
+ OSMO_ASSERT(imsi);
+
+ llist_for_each_entry(p, &proxy->pending_gsup_reqs, entry) {
+ if (strcmp(p->req->gsup.imsi, imsi))
+ continue;
+ return true;
+ }
+ return false;
+}
+
+/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. On failure, the remote_hlr may be passed
+ * NULL. */
+static void proxy_deferred_gsup_req_pop(struct proxy *proxy, const char *imsi, struct remote_hlr *remote_hlr)
+{
+ struct proxy_pending_gsup_req *p, *n;
+ OSMO_ASSERT(imsi);
+
+ llist_for_each_entry_safe(p, n, &proxy->pending_gsup_reqs, entry) {
+ if (strcmp(p->req->gsup.imsi, imsi))
+ continue;
+
+ proxy_pending_req_remote_hlr_connect_result(p->req, remote_hlr);
+ p->req = NULL;
+ llist_del(&p->entry);
+ talloc_free(p);
+ }
+}
+
+static bool proxy_subscr_matches_imsi(const struct proxy_subscr *proxy_subscr, const char *imsi)
+{
+ if (!proxy_subscr || !imsi)
+ return false;
+ return strcmp(proxy_subscr->imsi, imsi) == 0;
+}
+
+static bool proxy_subscr_matches_msisdn(const struct proxy_subscr *proxy_subscr, const char *msisdn)
+{
+ if (!proxy_subscr || !msisdn)
+ return false;
+ return strcmp(proxy_subscr->msisdn, msisdn) == 0;
+}
+
+static struct proxy_subscr_listentry *_proxy_get_by_imsi(struct proxy *proxy, const char *imsi)
+{
+ struct proxy_subscr_listentry *e;
+ if (!proxy)
+ return NULL;
+ llist_for_each_entry(e, &proxy->subscr_list, entry) {
+ if (proxy_subscr_matches_imsi(&e->data, imsi))
+ return e;
+ }
+ return NULL;
+}
+
+static struct proxy_subscr_listentry *_proxy_get_by_msisdn(struct proxy *proxy, const char *msisdn)
+{
+ struct proxy_subscr_listentry *e;
+ if (!proxy)
+ return NULL;
+ llist_for_each_entry(e, &proxy->subscr_list, entry) {
+ if (proxy_subscr_matches_msisdn(&e->data, msisdn))
+ return e;
+ }
+ return NULL;
+}
+
+int proxy_subscr_get_by_imsi(struct proxy_subscr *dst, struct proxy *proxy, const char *imsi)
+{
+ struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, imsi);
+ if (!e)
+ return -ENOENT;
+ *dst = e->data;
+ return 0;
+}
+
+int proxy_subscr_get_by_msisdn(struct proxy_subscr *dst, struct proxy *proxy, const char *msisdn)
+{
+ struct proxy_subscr_listentry *e = _proxy_get_by_msisdn(proxy, msisdn);
+ if (!e)
+ return -ENOENT;
+ *dst = e->data;
+ return 0;
+}
+
+int proxy_subscr_create_or_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr)
+{
+ struct proxy_subscr_listentry *e = _proxy_get_by_imsi(proxy, proxy_subscr->imsi);
+ if (!e) {
+ /* Does not exist yet */
+ e = talloc_zero(proxy, struct proxy_subscr_listentry);
+ llist_add(&e->entry, &proxy->subscr_list);
+ }
+ e->data = *proxy_subscr;
+ timestamp_update(&e->last_update);
+ return 0;
+}
+
+int _proxy_subscr_del(struct proxy_subscr_listentry *e)
+{
+ llist_del(&e->entry);
+ return 0;
+}
+
+int proxy_subscr_del(struct proxy *proxy, const char *imsi)
+{
+ struct proxy_subscr_listentry *e;
+ proxy_deferred_gsup_req_pop(proxy, imsi, NULL);
+ e = _proxy_get_by_imsi(proxy, imsi);
+ if (!e)
+ return -ENOENT;
+ return _proxy_subscr_del(e);
+}
+
+/* Discard stale proxy entries. */
+static void proxy_cleanup(void *proxy_v)
+{
+ struct proxy *proxy = proxy_v;
+ struct proxy_subscr_listentry *e, *n;
+ uint32_t age;
+ llist_for_each_entry_safe(e, n, &proxy->subscr_list, entry) {
+ if (!timestamp_age(&e->last_update, &age))
+ LOGP(DDGSM, LOGL_ERROR, "Invalid timestamp, deleting proxy entry\n");
+ else if (age <= proxy->fresh_time)
+ continue;
+ LOG_PROXY_SUBSCR(&e->data, LOGL_INFO, "proxy entry timed out, deleting\n");
+ _proxy_subscr_del(e);
+ }
+ if (proxy->gc_period)
+ osmo_timer_schedule(&proxy->gc_timer, proxy->gc_period, 0);
+ else
+ LOGP(DDGSM, LOGL_NOTICE, "Proxy cleanup is switched off (gc_period == 0)\n");
+}
+
+void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period)
+{
+ proxy->gc_period = gc_period;
+ proxy_cleanup(proxy);
+}
+
+void proxy_init(struct osmo_gsup_server *gsup_server_to_vlr)
+{
+ OSMO_ASSERT(!gsup_server_to_vlr->proxy);
+ struct proxy *proxy = talloc_zero(gsup_server_to_vlr, struct proxy);
+ *proxy = (struct proxy){
+ .gsup_server_to_vlr = gsup_server_to_vlr,
+ .fresh_time = 60*60,
+ .gc_period = 60,
+ };
+ INIT_LLIST_HEAD(&proxy->subscr_list);
+ INIT_LLIST_HEAD(&proxy->pending_gsup_reqs);
+
+ osmo_timer_setup(&proxy->gc_timer, proxy_cleanup, proxy);
+ /* Invoke to trigger the first timer schedule */
+ proxy_set_gc_period(proxy, proxy->gc_period);
+ gsup_server_to_vlr->proxy = proxy;
+}
+
+void proxy_del(struct proxy *proxy)
+{
+ osmo_timer_del(&proxy->gc_timer);
+ talloc_free(proxy);
+}
+
+/* All GSUP messages sent to the remote HLR pass through this function, to modify the subscriber state or disallow
+ * sending the message. Return 0 to allow sending the message. */
+static int proxy_acknowledge_gsup_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ const struct osmo_gsup_req *req)
+{
+ struct proxy_subscr proxy_subscr_new = *proxy_subscr;
+ bool ps;
+ bool cs;
+ int rc;
+
+ if (req->source_name.type != OSMO_GSUP_PEER_ID_IPA_NAME) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_ERROR,
+ "Unsupported GSUP peer id type: %s\n",
+ osmo_gsup_peer_id_type_name(req->source_name.type));
+ return -ENOTSUP;
+ }
+
+ switch (req->gsup.message_type) {
+
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
+ /* Store the CS and PS VLR name in vlr_name_preliminary to later update the right {cs,ps} LU timestamp
+ * when receiving an OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT. Store in vlr_name_preliminary so that in
+ * case the LU fails, we keep the vlr_name intact. */
+ switch (req->gsup.cn_domain) {
+ case OSMO_GSUP_CN_DOMAIN_CS:
+ proxy_subscr_new.cs.vlr_name_preliminary = req->source_name.ipa_name;
+ break;
+ case OSMO_GSUP_CN_DOMAIN_PS:
+ proxy_subscr_new.ps.vlr_name_preliminary = req->source_name.ipa_name;
+ break;
+ default:
+ break;
+ }
+
+ ps = cs = false;
+ if (osmo_ipa_name_cmp(&proxy_subscr_new.cs.vlr_name_preliminary, &proxy_subscr->cs.vlr_name_preliminary))
+ cs = true;
+ if (osmo_ipa_name_cmp(&proxy_subscr_new.ps.vlr_name_preliminary, &proxy_subscr->ps.vlr_name_preliminary))
+ ps = true;
+
+ if (!(cs || ps)) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "VLR names remain unchanged\n");
+ break;
+ }
+
+ rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new);
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, rc ? LOGL_ERROR : LOGL_INFO,
+ "%s: preliminary VLR name for%s%s to %s\n",
+ rc ? "failed to update" : "updated",
+ cs ? " CS" : "", ps ? " PS" : "",
+ osmo_gsup_peer_id_to_str(&req->source_name));
+ break;
+ /* TODO: delete proxy entry in case of a Purge Request? */
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* All GSUP messages received from the remote HLR to be sent to a local MSC pass through this function, to modify the
+ * subscriber state or disallow sending the message. Return 0 to allow sending the message.
+ * The local MSC shall be indicated by gsup.destination_name. */
+static int proxy_acknowledge_gsup_from_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ const struct osmo_gsup_message *gsup,
+ struct remote_hlr *from_remote_hlr,
+ const struct osmo_ipa_name *destination,
+ const struct osmo_ipa_name *via_peer)
+{
+ struct proxy_subscr proxy_subscr_new = *proxy_subscr;
+ bool ps;
+ bool cs;
+ bool vlr_name_changed_cs = false;
+ bool vlr_name_changed_ps = false;
+ int rc;
+ struct osmo_ipa_name via_proxy = {};
+ if (osmo_ipa_name_cmp(via_peer, destination))
+ via_proxy = *via_peer;
+
+ switch (gsup->message_type) {
+ case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+ /* Remember the MSISDN of the subscriber. This does not need to be a preliminary record, because when
+ * the HLR tells us about subscriber data, it is definitive info and there is no ambiguity (like there
+ * would be with failed LU attempts from various sources). */
+ if (!gsup->msisdn_enc_len)
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_DEBUG, "No MSISDN in this Insert Data Request\n");
+ else if (gsm48_decode_bcd_number2(proxy_subscr_new.msisdn, sizeof(proxy_subscr_new.msisdn),
+ gsup->msisdn_enc, gsup->msisdn_enc_len, 0))
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "Failed to decode MSISDN\n");
+ else if (!osmo_msisdn_str_valid(proxy_subscr_new.msisdn))
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "invalid MSISDN: %s\n",
+ osmo_quote_str_c(OTC_SELECT, proxy_subscr_new.msisdn, -1));
+ else if (!strcmp(proxy_subscr->msisdn, proxy_subscr_new.msisdn))
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_DEBUG, "already have MSISDN = %s\n",
+ proxy_subscr_new.msisdn);
+ else if (proxy_subscr_create_or_update(proxy, &proxy_subscr_new))
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR, "failed to update MSISDN to %s\n",
+ proxy_subscr_new.msisdn);
+ else
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_INFO, "stored MSISDN=%s\n",
+ proxy_subscr_new.msisdn);
+ break;
+
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+ /* Update the Location Updating timestamp */
+ cs = ps = false;
+ if (!osmo_ipa_name_cmp(destination, &proxy_subscr->cs.vlr_name_preliminary)) {
+ timestamp_update(&proxy_subscr_new.cs.last_lu);
+ proxy_subscr_new.cs.vlr_name_preliminary = (struct osmo_ipa_name){};
+ vlr_name_changed_cs =
+ osmo_ipa_name_cmp(&proxy_subscr->cs.vlr_name, destination)
+ || osmo_ipa_name_cmp(&proxy_subscr->cs.vlr_via_proxy, &via_proxy);
+ proxy_subscr_new.cs.vlr_name = *destination;
+ proxy_subscr_new.cs.vlr_via_proxy = via_proxy;
+ cs = true;
+ }
+ if (!osmo_ipa_name_cmp(destination, &proxy_subscr->ps.vlr_name_preliminary)) {
+ timestamp_update(&proxy_subscr_new.ps.last_lu);
+ proxy_subscr_new.ps.vlr_name_preliminary = (struct osmo_ipa_name){};
+ proxy_subscr_new.ps.vlr_name = *destination;
+ vlr_name_changed_ps =
+ osmo_ipa_name_cmp(&proxy_subscr->ps.vlr_name, destination)
+ || osmo_ipa_name_cmp(&proxy_subscr->ps.vlr_via_proxy, &via_proxy);
+ proxy_subscr_new.ps.vlr_via_proxy = via_proxy;
+ ps = true;
+ }
+ if (!(cs || ps)) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
+ "destination is neither CS nor PS VLR: %s\n",
+ osmo_ipa_name_to_str(destination));
+ return GMM_CAUSE_PROTO_ERR_UNSPEC;
+ }
+ rc = proxy_subscr_create_or_update(proxy, &proxy_subscr_new);
+
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, rc ? LOGL_ERROR : LOGL_INFO,
+ "%s LU: timestamp for%s%s%s%s%s%s%s%s%s%s\n",
+ rc ? "failed to update" : "updated",
+ cs ? " CS" : "", ps ? " PS" : "",
+ vlr_name_changed_cs? ", CS VLR=" : "",
+ vlr_name_changed_cs? osmo_ipa_name_to_str(&proxy_subscr_new.cs.vlr_name) : "",
+ proxy_subscr_new.cs.vlr_via_proxy.len ? " via proxy " : "",
+ proxy_subscr_new.cs.vlr_via_proxy.len ?
+ osmo_ipa_name_to_str(&proxy_subscr_new.cs.vlr_via_proxy) : "",
+ vlr_name_changed_ps? ", PS VLR=" : "",
+ vlr_name_changed_ps? osmo_ipa_name_to_str(&proxy_subscr_new.ps.vlr_name) : "",
+ proxy_subscr_new.ps.vlr_via_proxy.len ? " via proxy " : "",
+ proxy_subscr_new.ps.vlr_via_proxy.len ?
+ osmo_ipa_name_to_str(&proxy_subscr_new.ps.vlr_via_proxy) : ""
+ );
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void proxy_remote_hlr_connect_result_cb(const struct osmo_sockaddr_str *addr, struct remote_hlr *remote_hlr,
+ void *data)
+{
+ struct proxy *proxy = data;
+ struct proxy_subscr_listentry *e;
+ if (!proxy)
+ return;
+ llist_for_each_entry(e, &proxy->subscr_list, entry) {
+ if (!osmo_sockaddr_str_cmp(addr, &e->data.remote_hlr_addr)) {
+ proxy_deferred_gsup_req_pop(proxy, e->data.imsi, remote_hlr);
+ }
+ }
+}
+
+/* Store the remote HLR's GSUP address for this proxy subscriber.
+ * This can be set before the remote_hlr is connected, or after.
+ * And, this can be set before the gsup_req has been queued for this HLR, or after.
+ */
+void proxy_subscr_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ const struct osmo_sockaddr_str *remote_hlr_addr)
+{
+ struct proxy_subscr proxy_subscr_new;
+
+ if (osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
+ if (!osmo_sockaddr_str_cmp(remote_hlr_addr, &proxy_subscr->remote_hlr_addr)) {
+ /* Already have this remote address */
+ return;
+ } else {
+ LOG_PROXY_SUBSCR(proxy_subscr, LOGL_NOTICE,
+ "Remote HLR address changes to " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(remote_hlr_addr));
+ }
+ }
+
+ /* Store the address. Make a copy to modify. */
+ proxy_subscr_new = *proxy_subscr;
+ proxy_subscr = &proxy_subscr_new;
+ proxy_subscr_new.remote_hlr_addr = *remote_hlr_addr;
+
+ if (proxy_subscr_create_or_update(proxy, proxy_subscr)) {
+ LOG_PROXY_SUBSCR(proxy_subscr, LOGL_ERROR, "Failed to store proxy entry for remote HLR\n");
+ /* If no remote HLR is known for the IMSI, the proxy entry is pointless. */
+ proxy_subscr_del(proxy, proxy_subscr->imsi);
+ return;
+ }
+ LOG_PROXY_SUBSCR(proxy_subscr, LOGL_DEBUG, "Remote HLR resolved, stored address\n");
+
+ /* If any messages for this HLR are already spooled, connect now. Otherwise wait for
+ * proxy_subscr_forward_to_remote_hlr() to connect then. */
+ if (proxy_deferred_gsup_req_waiting(proxy, proxy_subscr->imsi))
+ remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, true,
+ proxy_remote_hlr_connect_result_cb, proxy);
+}
+
+int proxy_subscr_forward_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, struct osmo_gsup_req *req)
+{
+ struct remote_hlr *remote_hlr;
+ int rc;
+
+ rc = proxy_acknowledge_gsup_to_remote_hlr(proxy, proxy_subscr, req);
+ if (rc) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_PROTO_ERR_UNSPEC, "Proxy does not allow this message");
+ return rc;
+ }
+
+ if (!osmo_sockaddr_str_is_nonzero(&proxy_subscr->remote_hlr_addr)) {
+ /* We don't know the remote target yet. Still waiting for an MS lookup response, which will end up
+ * calling proxy_subscr_remote_hlr_resolved(). See dgsm.c. */
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "deferring until remote HLR is known\n");
+ proxy_deferred_gsup_req_add(proxy, req);
+ return 0;
+ }
+
+ if (!osmo_gsup_peer_id_is_empty(&req->via_proxy)) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_INFO, "VLR->HLR: forwarding from %s via proxy %s\n",
+ osmo_gsup_peer_id_to_str(&req->source_name),
+ osmo_gsup_peer_id_to_str(&req->via_proxy));
+ } else {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_INFO, "VLR->HLR: forwarding from %s\n",
+ osmo_gsup_peer_id_to_str(&req->source_name));
+ }
+
+ /* We could always store in the defer queue and empty the queue if the connection is already up.
+ * Slight optimisation: if the remote_hlr is already up and running, skip the defer queue.
+ * First ask for an existing remote_hlr. */
+ remote_hlr = remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, false, NULL, NULL);
+ if (remote_hlr && remote_hlr_is_up(remote_hlr)) {
+ proxy_pending_req_remote_hlr_connect_result(req, remote_hlr);
+ return 0;
+ }
+
+ /* Not existing or not up. Defer req and ask to be notified when it is up.
+ * If the remote_hlr exists but is not connected yet, there should actually already be a pending
+ * proxy_remote_hlr_connect_result_cb queued, but it doesn't hurt to do that more often. */
+ proxy_deferred_gsup_req_add(proxy, req);
+ remote_hlr_get_or_connect(&proxy_subscr->remote_hlr_addr, true,
+ proxy_remote_hlr_connect_result_cb, proxy);
+ return 0;
+}
+
+int proxy_subscr_forward_to_vlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ const struct osmo_gsup_message *gsup, struct remote_hlr *from_remote_hlr)
+{
+ struct osmo_ipa_name destination;
+ struct osmo_gsup_conn *vlr_conn;
+ struct msgb *msg;
+
+ if (osmo_ipa_name_set(&destination, gsup->destination_name, gsup->destination_name_len)) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
+ "no valid Destination Name IE, cannot route to VLR.\n");
+ return GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ /* Route to MSC/SGSN that we're proxying for */
+ vlr_conn = gsup_route_find_by_ipa_name(proxy->gsup_server_to_vlr, &destination);
+ if (!vlr_conn) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
+ "Destination VLR unreachable: %s\n", osmo_ipa_name_to_str(&destination));
+ return GMM_CAUSE_MSC_TEMP_NOTREACH;
+ }
+
+ if (proxy_acknowledge_gsup_from_remote_hlr(proxy, proxy_subscr, gsup, from_remote_hlr, &destination,
+ &vlr_conn->peer_name)) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
+ "Proxy does not allow forwarding this message\n");
+ return GMM_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ msg = osmo_gsup_msgb_alloc("GSUP proxy to VLR");
+ if (osmo_gsup_encode(msg, gsup)) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_ERROR,
+ "Failed to re-encode GSUP message, cannot forward\n");
+ return GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, gsup, LOGL_INFO, "VLR<-HLR: forwarding to %s%s%s\n",
+ osmo_ipa_name_to_str(&destination),
+ osmo_ipa_name_cmp(&destination, &vlr_conn->peer_name) ? " via " : "",
+ osmo_ipa_name_cmp(&destination, &vlr_conn->peer_name) ?
+ osmo_ipa_name_to_str(&vlr_conn->peer_name) : "");
+ return osmo_gsup_conn_send(vlr_conn, msg);
+}
diff --git a/src/remote_hlr.c b/src/remote_hlr.c
new file mode 100644
index 0000000..e2e7d47
--- /dev/null
+++ b/src/remote_hlr.c
@@ -0,0 +1,252 @@
+/* Copyright 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/gsm/gsm23003.h>
+#include <osmocom/abis/ipa.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include <osmocom/hlr/logging.h>
+#include <osmocom/hlr/hlr.h>
+#include <osmocom/hlr/gsup_server.h>
+#include <osmocom/hlr/gsup_router.h>
+#include <osmocom/hlr/remote_hlr.h>
+#include <osmocom/hlr/proxy.h>
+
+static LLIST_HEAD(remote_hlrs);
+
+static void remote_hlr_err_reply(struct remote_hlr *rh, const struct osmo_gsup_message *gsup_orig,
+ enum gsm48_gmm_cause cause)
+{
+ struct osmo_gsup_message gsup_reply;
+
+ /* No need to answer if we couldn't parse an ERROR message type, only REQUESTs need an error reply. */
+ if (!OSMO_GSUP_IS_MSGT_REQUEST(gsup_orig->message_type))
+ return;
+
+ gsup_reply = (struct osmo_gsup_message){
+ .cause = cause,
+ .message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type),
+ .message_class = gsup_orig->message_class,
+
+ /* RP-Message-Reference is mandatory for SM Service */
+ .sm_rp_mr = gsup_orig->sm_rp_mr,
+ };
+
+ OSMO_STRLCPY_ARRAY(gsup_reply.imsi, gsup_orig->imsi);
+
+ /* For SS/USSD, it's important to keep both session state and ID IEs */
+ if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) {
+ gsup_reply.session_state = OSMO_GSUP_SESSION_STATE_END;
+ gsup_reply.session_id = gsup_orig->session_id;
+ }
+
+ if (osmo_gsup_client_enc_send(rh->gsupc, &gsup_reply))
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
+ osmo_quote_str(gsup_orig->imsi, -1));
+}
+
+/* We are receiving a GSUP message from a remote HLR to go back to a local MSC.
+ * The local MSC shall be indicated by gsup.destination_name. */
+static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg)
+{
+ struct remote_hlr *rh = gsupc->data;
+ struct proxy_subscr proxy_subscr;
+ struct osmo_gsup_message gsup;
+ int rc;
+
+ rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
+ if (rc < 0) {
+ LOG_REMOTE_HLR(rh, LOGL_ERROR, "Failed to decode GSUP message: '%s' (%d) [ %s]\n",
+ get_value_string(gsm48_gmm_cause_names, -rc),
+ -rc, osmo_hexdump(msg->data, msg->len));
+ return rc;
+ }
+
+ if (!osmo_imsi_str_valid(gsup.imsi)) {
+ LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "Invalid IMSI\n");
+ remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_INV_MAND_INFO);
+ return -GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ if (proxy_subscr_get_by_imsi(&proxy_subscr, g_hlr->gs->proxy, gsup.imsi)) {
+ LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "No proxy entry for this IMSI\n");
+ remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_NET_FAIL);
+ return -GMM_CAUSE_NET_FAIL;
+ }
+
+ rc = proxy_subscr_forward_to_vlr(g_hlr->gs->proxy, &proxy_subscr, &gsup, rh);
+ if (rc) {
+ LOG_REMOTE_HLR_MSG(rh, &gsup, LOGL_ERROR, "Failed to forward GSUP message towards VLR\n");
+ remote_hlr_err_reply(rh, &gsup, GMM_CAUSE_NET_FAIL);
+ return -GMM_CAUSE_NET_FAIL;
+ }
+ return 0;
+}
+
+struct remote_hlr_pending_up {
+ struct llist_head entry;
+ remote_hlr_connect_result_cb_t connect_result_cb;
+ void *data;
+};
+
+static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up)
+{
+ struct remote_hlr *remote_hlr = gsupc->data;
+ struct remote_hlr_pending_up *p, *n;
+ if (!up) {
+ LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link to remote HLR is down, removing GSUP client\n");
+ remote_hlr_destroy(remote_hlr);
+ return false;
+ }
+
+ LOG_REMOTE_HLR(remote_hlr, LOGL_NOTICE, "link up\n");
+ llist_for_each_entry_safe(p, n, &remote_hlr->pending_up_callbacks, entry) {
+ if (p->connect_result_cb)
+ p->connect_result_cb(&remote_hlr->addr, remote_hlr, p->data);
+ llist_del(&p->entry);
+ }
+ return true;
+}
+
+bool remote_hlr_is_up(struct remote_hlr *remote_hlr)
+{
+ return remote_hlr && remote_hlr->gsupc && remote_hlr->gsupc->is_connected;
+}
+
+struct remote_hlr *remote_hlr_get_or_connect(const struct osmo_sockaddr_str *addr, bool connect,
+ remote_hlr_connect_result_cb_t connect_result_cb, void *data)
+{
+ struct remote_hlr *rh = NULL;
+ struct remote_hlr *rh_i;
+ struct osmo_gsup_client_config cfg;
+
+ llist_for_each_entry(rh_i, &remote_hlrs, entry) {
+ if (!osmo_sockaddr_str_cmp(&rh_i->addr, addr)) {
+ rh = rh_i;
+ break;
+ }
+ }
+
+ if (rh)
+ goto add_result_cb;
+
+ if (!connect) {
+ if (connect_result_cb)
+ connect_result_cb(addr, NULL, data);
+ return NULL;
+ }
+
+ /* Doesn't exist yet, create a GSUP client to remote HLR. */
+ cfg = (struct osmo_gsup_client_config){
+ .ipa_dev = &g_hlr->gsup_unit_name,
+ .ip_addr = addr->ip,
+ .tcp_port = addr->port,
+ .oapc_config = NULL,
+ .read_cb = remote_hlr_rx,
+ .up_down_cb = remote_hlr_up_down,
+ .data = rh,
+ };
+ rh = talloc_zero(dgsm_ctx, struct remote_hlr);
+ OSMO_ASSERT(rh);
+ *rh = (struct remote_hlr){
+ .addr = *addr,
+ .gsupc = osmo_gsup_client_create3(rh, &cfg),
+ };
+ INIT_LLIST_HEAD(&rh->pending_up_callbacks);
+ if (!rh->gsupc) {
+ LOGP(DDGSM, LOGL_ERROR,
+ "Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(addr));
+ talloc_free(rh);
+ if (connect_result_cb)
+ connect_result_cb(addr, NULL, data);
+ return NULL;
+ }
+
+ rh->gsupc->data = rh;
+ llist_add(&rh->entry, &remote_hlrs);
+
+add_result_cb:
+ if (connect_result_cb) {
+ if (remote_hlr_is_up(rh)) {
+ connect_result_cb(addr, rh, data);
+ } else {
+ struct remote_hlr_pending_up *p;
+ p = talloc_zero(rh, struct remote_hlr_pending_up);
+ OSMO_ASSERT(p);
+ p->connect_result_cb = connect_result_cb;
+ p->data = data;
+ llist_add_tail(&p->entry, &rh->pending_up_callbacks);
+ }
+ }
+ return rh;
+}
+
+void remote_hlr_destroy(struct remote_hlr *remote_hlr)
+{
+ osmo_gsup_client_destroy(remote_hlr->gsupc);
+ remote_hlr->gsupc = NULL;
+ llist_del(&remote_hlr->entry);
+ talloc_free(remote_hlr);
+}
+
+/* This function takes ownership of the msg, do not free it after passing to this function. */
+int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg)
+{
+ int rc = osmo_gsup_client_send(remote_hlr->gsupc, msg);
+ if (rc) {
+ LOGP(DDGSM, LOGL_ERROR, "Failed to send GSUP message to " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&remote_hlr->addr));
+ }
+ return rc;
+}
+
+/* A GSUP message was received from the MS/MSC side, forward it to the remote HLR. */
+void remote_hlr_gsup_forward_to_remote_hlr(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req,
+ struct osmo_gsup_message *modified_gsup)
+{
+ int rc;
+ struct msgb *msg;
+ /* To forward to a remote HLR, we need to indicate the source MSC's name in the Source Name IE to make sure the
+ * reply can be routed back. Store the sender MSC in gsup->source_name -- the remote HLR is required to return
+ * this as gsup->destination_name so that the reply gets routed to the original MSC. */
+ struct osmo_gsup_message forward;
+ if (modified_gsup)
+ forward = *modified_gsup;
+ else
+ forward = req->gsup;
+
+ if (req->source_name.type != OSMO_GSUP_PEER_ID_IPA_NAME) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
+ osmo_gsup_peer_id_type_name(req->source_name.type));
+ return;
+ }
+ forward.source_name = req->source_name.ipa_name.val;
+ forward.source_name_len = req->source_name.ipa_name.len;
+
+ msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
+ rc = osmo_gsup_encode(msg, &forward);
+ if (rc) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Failed to encode GSUP message for forwarding");
+ return;
+ }
+ remote_hlr_msgb_send(remote_hlr, msg);
+ osmo_gsup_req_free(req);
+}