aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2019-11-20 03:35:37 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2019-12-16 16:56:33 +0100
commit62d916f3cd6a71526a7ad196178ddde8806c5fcf (patch)
tree7de91a599f2b44ec3e24e87102bf207e14e8048a
parent3ad481afbfdf078d12eb5f9e215c94d06cb1c577 (diff)
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
-rw-r--r--include/osmocom/hlr/Makefile.am3
-rw-r--r--include/osmocom/hlr/dgsm.h45
-rw-r--r--include/osmocom/hlr/gsup_server.h3
-rw-r--r--include/osmocom/hlr/hlr.h25
-rw-r--r--include/osmocom/hlr/hlr_vty.h1
-rw-r--r--include/osmocom/hlr/logging.h1
-rw-r--r--include/osmocom/hlr/proxy.h101
-rw-r--r--include/osmocom/hlr/remote_hlr.h48
-rw-r--r--src/Makefile.am3
-rw-r--r--src/dgsm.c266
-rw-r--r--src/dgsm_vty.c200
-rw-r--r--src/hlr.c20
-rw-r--r--src/hlr_vty.c19
-rw-r--r--src/logging.c6
-rw-r--r--src/mslookup_server.c74
-rw-r--r--src/mslookup_server_mdns.c5
-rw-r--r--src/proxy.c552
-rw-r--r--src/remote_hlr.c210
-rw-r--r--tests/test_nodes.vty338
19 files changed, 1914 insertions, 6 deletions
diff --git a/include/osmocom/hlr/Makefile.am b/include/osmocom/hlr/Makefile.am
index b24f084..aceda4a 100644
--- a/include/osmocom/hlr/Makefile.am
+++ b/include/osmocom/hlr/Makefile.am
@@ -2,6 +2,7 @@ noinst_HEADERS = \
auc.h \
ctrl.h \
db.h \
+ dgsm.h \
gsup_router.h \
gsup_server.h \
hlr.h \
@@ -12,6 +13,8 @@ noinst_HEADERS = \
lu_fsm.h \
mslookup_server.h \
mslookup_server_mdns.h \
+ proxy.h \
rand.h \
+ remote_hlr.h \
timestamp.h \
$(NULL)
diff --git a/include/osmocom/hlr/dgsm.h b/include/osmocom/hlr/dgsm.h
new file mode 100644
index 0000000..f06e381
--- /dev/null
+++ b/include/osmocom/hlr/dgsm.h
@@ -0,0 +1,45 @@
+/* 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/>.
+ *
+ */
+
+#pragma once
+
+#include <osmocom/mslookup/mslookup.h>
+#include <osmocom/hlr/gsup_server.h>
+#include <osmocom/gsupclient/gsup_peer_id.h>
+#include <osmocom/gsupclient/gsup_req.h>
+
+#define LOG_DGSM(imsi, level, fmt, args...) \
+ LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args)
+
+struct vty;
+struct remote_hlr;
+struct hlr_subscriber;
+
+extern void *dgsm_ctx;
+
+void dgsm_init(void *ctx);
+void dgsm_start(void *ctx);
+void dgsm_stop();
+
+bool dgsm_check_forward_gsup_msg(struct osmo_gsup_req *req);
+
+void dgsm_vty_init();
+void dgsm_mdns_client_config_apply(void);
+
+bool hlr_subscr_lu_age(const struct hlr_subscriber *subscr, uint32_t *age_p);
diff --git a/include/osmocom/hlr/gsup_server.h b/include/osmocom/hlr/gsup_server.h
index 0ecf42c..b7cfb89 100644
--- a/include/osmocom/hlr/gsup_server.h
+++ b/include/osmocom/hlr/gsup_server.h
@@ -27,6 +27,9 @@ struct osmo_gsup_server {
struct ipa_server_link *link;
osmo_gsup_read_cb_t read_cb;
struct llist_head routes;
+
+ /* Proxy requests from this server's clients to remote GSUP servers. */
+ struct proxy *proxy;
};
diff --git a/include/osmocom/hlr/hlr.h b/include/osmocom/hlr/hlr.h
index e24eaf7..2294bb6 100644
--- a/include/osmocom/hlr/hlr.h
+++ b/include/osmocom/hlr/hlr.h
@@ -28,6 +28,8 @@
#include <osmocom/core/tdef.h>
#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/hlr/dgsm.h>
+
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
struct hlr_euse;
@@ -86,6 +88,29 @@ struct hlr {
struct osmo_mslookup_server_mdns *running;
} mdns;
} server;
+
+ /* The mslookup client in osmo-hlr is used to find out which remote HLRs service a locally unknown IMSI.
+ * (It may also be used to resolve recipients for SMS-over-GSUP in the future.) */
+ struct {
+ /* Whether to proxy/forward to remote HLRs */
+ bool enable;
+
+ /* If this is set, all GSUP for unknown IMSIs is forwarded directly to this GSUP address,
+ * unconditionally. */
+ struct osmo_sockaddr_str gsup_gateway_proxy;
+
+ /* mslookup client request handling */
+ unsigned int result_timeout_milliseconds;
+
+ struct osmo_mslookup_client *client;
+ struct {
+ /* Whether to use mDNS for IMSI MS Lookup */
+ bool enable;
+ struct osmo_sockaddr_str query_addr;
+ char *domain_suffix;
+ struct osmo_mslookup_client_method *running;
+ } mdns;
+ } client;
} mslookup;
};
diff --git a/include/osmocom/hlr/hlr_vty.h b/include/osmocom/hlr/hlr_vty.h
index 0ba9821..c026d91 100644
--- a/include/osmocom/hlr/hlr_vty.h
+++ b/include/osmocom/hlr/hlr_vty.h
@@ -34,6 +34,7 @@ enum hlr_vty_node {
MSLOOKUP_NODE,
MSLOOKUP_SERVER_NODE,
MSLOOKUP_SERVER_MSC_NODE,
+ MSLOOKUP_CLIENT_NODE,
};
int hlr_vty_is_config_node(struct vty *vty, int node);
diff --git a/include/osmocom/hlr/logging.h b/include/osmocom/hlr/logging.h
index 4e0a25c..a8081af 100644
--- a/include/osmocom/hlr/logging.h
+++ b/include/osmocom/hlr/logging.h
@@ -10,6 +10,7 @@ enum {
DSS,
DMSLOOKUP,
DLU,
+ DDGSM,
};
extern const struct log_info hlr_log_info;
diff --git a/include/osmocom/hlr/proxy.h b/include/osmocom/hlr/proxy.h
new file mode 100644
index 0000000..8412dd2
--- /dev/null
+++ b/include/osmocom/hlr/proxy.h
@@ -0,0 +1,101 @@
+/* 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/>.
+ *
+ */
+
+#pragma once
+
+#include <time.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/gsupclient/gsup_peer_id.h>
+#include <osmocom/hlr/timestamp.h>
+
+struct osmo_gsup_req;
+struct remote_hlr;
+
+struct proxy_pending_gsup_req {
+ struct llist_head entry;
+ struct osmo_gsup_req *req;
+ timestamp_t received_at;
+};
+
+struct proxy {
+ struct llist_head subscr_list;
+ struct llist_head pending_gsup_reqs;
+
+ /* When messages arrive back from a remote HLR that this is the proxy for, reach the VLR to forward the response
+ * to via this osmo_gsup_server. */
+ struct osmo_gsup_server *gsup_server_to_vlr;
+
+ /* How long to keep proxy entries without a refresh, in seconds. */
+ uint32_t fresh_time;
+
+ /* How often to garbage collect the proxy cache, period in seconds.
+ * To change this and take effect immediately, rather use proxy_set_gc_period(). */
+ uint32_t gc_period;
+
+ struct osmo_timer_list gc_timer;
+};
+
+struct proxy_subscr_domain_state {
+ struct osmo_ipa_name vlr_name;
+ timestamp_t last_lu;
+
+ /* The name from which an Update Location Request was received. Copied to vlr_name as soon as the LU is
+ * completed successfully. */
+ struct osmo_ipa_name vlr_name_preliminary;
+
+ /* Set if this is a middle proxy, i.e. a proxy behind another proxy.
+ * That is mostly to know whether the MS is attached at a local MSC/SGSN or further away.
+ * It could be a boolean, but store the full name for logging. Set only at successful LU acceptance. */
+ struct osmo_ipa_name vlr_via_proxy;
+};
+
+struct proxy_subscr {
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+ char msisdn[GSM23003_MSISDN_MAX_DIGITS+1];
+ struct osmo_sockaddr_str remote_hlr_addr;
+ struct proxy_subscr_domain_state cs, ps;
+};
+
+void proxy_init(struct osmo_gsup_server *gsup_server_to_vlr);
+void proxy_del(struct proxy *proxy);
+void proxy_set_gc_period(struct proxy *proxy, uint32_t gc_period);
+
+/* The API to access / modify proxy entries keeps the implementation opaque, to make sure that we can easily move proxy
+ * storage to SQLite db. */
+int proxy_subscr_get_by_imsi(struct proxy_subscr *dst, struct proxy *proxy, const char *imsi);
+int proxy_subscr_get_by_msisdn(struct proxy_subscr *dst, struct proxy *proxy, const char *msisdn);
+void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
+ bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
+ void *data);
+int proxy_subscr_create_or_update(struct proxy *proxy, const struct proxy_subscr *proxy_subscr);
+int proxy_subscr_del(struct proxy *proxy, const char *imsi);
+
+void proxy_subscr_forward_to_remote_hlr(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct osmo_gsup_req *req);
+void proxy_subscr_forward_to_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct remote_hlr *remote_hlr, struct osmo_gsup_req *req);
+
+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);
+
+void proxy_subscr_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct remote_hlr *remote_hlr);
+void proxy_subscr_remote_hlr_up(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct remote_hlr *remote_hlr);
diff --git a/include/osmocom/hlr/remote_hlr.h b/include/osmocom/hlr/remote_hlr.h
new file mode 100644
index 0000000..4f9f939
--- /dev/null
+++ b/include/osmocom/hlr/remote_hlr.h
@@ -0,0 +1,48 @@
+/* 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/>.
+ *
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/sockaddr_str.h>
+
+struct osmo_gsup_client;
+struct osmo_gsup_message;
+struct osmo_gsup_req;
+struct msgb;
+
+#define LOG_REMOTE_HLR(remote_hlr, level, fmt, args...) \
+ LOGP(DDGSM, level, "(Proxy HLR-" OSMO_SOCKADDR_STR_FMT ") " fmt, \
+ OSMO_SOCKADDR_STR_FMT_ARGS((remote_hlr) ? &(remote_hlr)->addr : NULL), ##args)
+
+#define LOG_REMOTE_HLR_MSG(remote_hlr, gsup_msg, level, fmt, args...) \
+ LOG_REMOTE_HLR(remote_hlr, level, "%s: " fmt, osmo_gsup_message_type_name((gsup_msg)->message_type), ##args)
+
+/* GSUP client link for proxying to a remote HLR. */
+struct remote_hlr {
+ struct llist_head entry;
+ struct osmo_sockaddr_str addr;
+ struct osmo_gsup_client *gsupc;
+};
+
+struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create);
+void remote_hlr_destroy(struct remote_hlr *remote_hlr);
+int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg);
+void remote_hlr_gsup_forward_to_remote_hlr(struct remote_hlr *remote_hlr, struct osmo_gsup_req *req);
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 @@ osmo_hlr_SOURCES = \
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..647e2ee
--- /dev/null
+++ b/src/dgsm.c
@@ -0,0 +1,266 @@
+/* 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 *use_addr;
+ struct remote_hlr *remote_hlr;
+
+ /* 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))
+ use_addr = &result->host_v4;
+ else if (osmo_sockaddr_str_is_nonzero(&result->host_v6))
+ use_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;
+ }
+
+ remote_hlr = remote_hlr_get(use_addr, true);
+ if (!remote_hlr) {
+ 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;
+ }
+
+ /* The remote HLR already exists and is connected. Messages for this IMSI were spooled because we did not know
+ * which remote HLR was responsible. Now we know, send this IMSI's messages now. */
+ LOG_DGSM(query->id.imsi, LOGL_DEBUG, "Resolved remote HLR, sending spooled GSUP messages: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, result));
+
+ proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, remote_hlr);
+
+ if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
+ LOG_REMOTE_HLR(remote_hlr, LOGL_DEBUG, "Waiting for link-up\n");
+ return;
+ }
+ proxy_subscr_remote_hlr_up(proxy, &proxy_subscr, remote_hlr);
+}
+
+/* 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)
+ goto yes_we_are_proxying;
+
+ /* 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)) {
+ LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Failed to create proxy entry\n");
+ return false;
+ }
+
+ /* Is a fixed gateway proxy configured? */
+ if (osmo_sockaddr_str_is_nonzero(&g_hlr->mslookup.client.gsup_gateway_proxy)) {
+ struct remote_hlr *gsup_gateway_proxy = remote_hlr_get(&g_hlr->mslookup.client.gsup_gateway_proxy, true);
+ if (!gsup_gateway_proxy) {
+ LOG_DGSM(req->gsup.imsi, LOGL_ERROR,
+ "Failed to set up fixed gateway proxy " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.client.gsup_gateway_proxy));
+ return false;
+ }
+
+ proxy_subscr_remote_hlr_resolved(proxy, &proxy_subscr, gsup_gateway_proxy);
+
+ /* Update info */
+ if (proxy_subscr_get_by_imsi(&proxy_subscr, proxy, req->gsup.imsi)) {
+ LOG_DGSM(req->gsup.imsi, LOGL_ERROR, "Proxy entry disappeared\n");
+ return false;
+ }
+ goto yes_we_are_proxying;
+ }
+
+ /* Kick off an mslookup for the remote HLR. */
+ if (!g_hlr->mslookup.client.client) {
+ LOG_GSUP_REQ(req, LOGL_DEBUG, "mslookup client not running, cannot query remote home HLR\n");
+ return false;
+ }
+
+ 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);
+ return false;
+ }
+
+yes_we_are_proxying:
+
+ /* If the remote HLR is already known, directly forward the GSUP message; otherwise, spool the GSUP message
+ * until the remote HLR will respond / until timeout aborts. */
+ proxy_subscr_forward_to_remote_hlr(proxy, &proxy_subscr, req);
+ 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 9b73f65..62f07c7 100644
--- a/src/dgsm_vty.c
+++ b/src/dgsm_vty.c
@@ -61,6 +61,26 @@ static int mslookup_server_mdns_bind(struct vty *vty, int argc, const char **arg
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 @@ static int mslookup_server_mdns_bind(struct vty *vty, int argc, const char **arg
#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",
+ 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)# ",
@@ -264,6 +322,81 @@ DEFUN(cfg_mslookup_server_msc_no_service_addr,
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,
+ cfg_mslookup_client_mdns_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,
+ cfg_mslookup_client_no_mdns_cmd,
+ "no mdns",
+ 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;
@@ -281,7 +414,8 @@ void config_write_msc_services(struct vty *vty, const char *indent, struct msloo
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);
@@ -321,6 +455,57 @@ int config_write_mslookup(struct vty *vty)
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 to %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;
}
@@ -360,6 +545,9 @@ void dgsm_vty_init(void)
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);
@@ -377,5 +565,15 @@ void dgsm_vty_init(void)
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_cmd);
+ install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_mdns_domain_suffix_cmd);
+ install_element(MSLOOKUP_CLIENT_NODE, &cfg_mslookup_client_no_mdns_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/hlr.c b/src/hlr.c
index 59ce4f5..0d8024f 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>
@@ -495,6 +498,15 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
}
}
+ /* 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;
+ }
+
switch (req->gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
@@ -695,6 +707,7 @@ int main(int argc, char **argv)
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;
@@ -705,6 +718,9 @@ int main(int argc, char **argv)
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);
@@ -760,10 +776,13 @@ int main(int argc, char **argv)
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);
@@ -780,6 +799,7 @@ int main(int argc, char **argv)
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 @@ DEFUN(cfg_hlr_gsup_bind_ip,
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 @@ void hlr_vty_init(void)
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 @@ const struct log_info_cat hlr_log_info_cat[] = {
.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 1fa802a..29768c8 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,
@@ -290,6 +291,68 @@ static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *qu
return true;
}
+
+/* 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 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));
+
+ 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)
@@ -297,6 +360,9 @@ static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
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 = {};
/* 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:
@@ -307,8 +373,14 @@ static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
* situations, compare the two entries.
*/
attached_here = subscriber_has_done_lu_here_hlr(query, &lu_age, &msc_name);
+ attached_here_proxy = subscriber_has_done_lu_here_proxy(query, &proxy_lu_age, &proxy_msc_name);
- /* 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 (attached_here && !msc_name.len) {
LOGP(DMSLOOKUP, LOGL_ERROR, "%s: attached here, but no VLR name known\n",
diff --git a/src/mslookup_server_mdns.c b/src/mslookup_server_mdns.c
index 7a02cb8..90547c9 100644
--- a/src/mslookup_server_mdns.c
+++ b/src/mslookup_server_mdns.c
@@ -126,10 +126,9 @@ void mslookup_server_mdns_config_apply()
/* Check whether to start/stop/restart mDNS server */
bool should_run;
bool should_stop;
- if (!g_hlr->mslookup.allow_startup)
- return;
- should_run = g_hlr->mslookup.server.enable && g_hlr->mslookup.server.mdns.enable;
+ should_run = g_hlr->mslookup.allow_startup
+ && g_hlr->mslookup.server.enable && g_hlr->mslookup.server.mdns.enable;
should_stop = g_hlr->mslookup.server.mdns.running
&& (!should_run
|| osmo_sockaddr_str_cmp(&g_hlr->mslookup.server.mdns.bind_addr,
diff --git a/src/proxy.c b/src/proxy.c
new file mode 100644
index 0000000..0d3fd13
--- /dev/null
+++ b/src/proxy.c
@@ -0,0 +1,552 @@
+/* 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)
+
+/* 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;
+};
+
+/* 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_defer_gsup_req(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);
+}
+
+/* Unable to resolve remote HLR for this IMSI, Answer with error back to the sender. */
+static void proxy_defer_gsup_message_err(struct proxy *proxy, struct proxy_pending_gsup_req *m)
+{
+ osmo_gsup_req_respond_err(m->req, GMM_CAUSE_IMSI_UNKNOWN, "could not reach home HLR");
+ m->req = NULL;
+}
+
+/* Forward spooled message for this IMSI to remote HLR. */
+static void proxy_defer_gsup_message_send(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct proxy_pending_gsup_req *m, struct remote_hlr *remote_hlr)
+{
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &m->req->gsup, LOGL_INFO, "Forwarding deferred message\n");
+ proxy_subscr_forward_to_remote_hlr_resolved(proxy, proxy_subscr, remote_hlr, m->req);
+ m->req = NULL;
+}
+
+/* Result of looking for remote HLR. If it failed, pass remote_hlr as NULL. On failure, the proxy_subscr and the
+ * remote_hlr may be passed NULL. The IMSI then reflects who the error was for. */
+static void proxy_defer_gsup_message_pop(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ const char *imsi, struct remote_hlr *remote_hlr)
+{
+ struct proxy_pending_gsup_req *m, *n;
+ if (!imsi && proxy_subscr)
+ imsi = proxy_subscr->imsi;
+ OSMO_ASSERT(imsi);
+
+ if (!remote_hlr)
+ LOGP(DDGSM, LOGL_ERROR, "IMSI-%s: No remote HLR found, dropping spooled GSUP messages\n", imsi);
+
+ llist_for_each_entry_safe(m, n, &proxy->pending_gsup_reqs, entry) {
+ if (strcmp(m->req->gsup.imsi, imsi))
+ continue;
+
+ if (!remote_hlr)
+ proxy_defer_gsup_message_err(proxy, m);
+ else
+ proxy_defer_gsup_message_send(proxy, proxy_subscr, m, remote_hlr);
+
+ llist_del(&m->entry);
+ talloc_free(m);
+ }
+}
+
+
+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;
+}
+
+void proxy_subscrs_get_by_remote_hlr(struct proxy *proxy, const struct osmo_sockaddr_str *remote_hlr_addr,
+ bool (*yield)(struct proxy *proxy, const struct proxy_subscr *subscr, void *data),
+ void *data)
+{
+ struct proxy_subscr_listentry *e;
+ if (!proxy)
+ return;
+ llist_for_each_entry(e, &proxy->subscr_list, entry) {
+ if (!osmo_sockaddr_str_cmp(remote_hlr_addr, &e->data.remote_hlr_addr)) {
+ if (!yield(proxy, &e->data, data))
+ return;
+ }
+ }
+}
+
+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_defer_gsup_message_pop(proxy, NULL, 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);
+}
+
+void proxy_subscr_remote_hlr_up(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct remote_hlr *remote_hlr)
+{
+ proxy_defer_gsup_message_pop(proxy, proxy_subscr, proxy_subscr->imsi, remote_hlr);
+}
+
+void proxy_subscr_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct remote_hlr *remote_hlr)
+{
+ 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_new.remote_hlr_addr = remote_hlr->addr;
+
+ if (proxy_subscr_create_or_update(proxy, &proxy_subscr_new)) {
+ 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_new.imsi);
+ return;
+ }
+ proxy_subscr = &proxy_subscr_new;
+ LOG_PROXY_SUBSCR(proxy_subscr, LOGL_DEBUG, "Remote HLR resolved, stored address\n");
+}
+
+/* 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;
+}
+
+
+void proxy_subscr_forward_to_remote_hlr_resolved(struct proxy *proxy, const struct proxy_subscr *proxy_subscr,
+ struct remote_hlr *remote_hlr, struct osmo_gsup_req *req)
+{
+ if (proxy_acknowledge_gsup_to_remote_hlr(proxy, proxy_subscr, req)) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_PROTO_ERR_UNSPEC, "Proxy does not allow this message");
+ return;
+ }
+
+ remote_hlr_gsup_forward_to_remote_hlr(remote_hlr, req);
+}
+
+void 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;
+
+ 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. */
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG, "deferring until remote HLR is known\n");
+ proxy_defer_gsup_req(proxy, req);
+ return;
+ }
+
+ 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));
+ }
+
+ remote_hlr = remote_hlr_get(&proxy_subscr->remote_hlr_addr, true);
+ if (!remote_hlr) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL,
+ "Proxy: Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT,
+ OSMO_SOCKADDR_STR_FMT_ARGS(&proxy_subscr->remote_hlr_addr));
+ return;
+ }
+
+ if (!remote_hlr->gsupc || !remote_hlr->gsupc->is_connected) {
+ /* GSUP link is still busy establishing... */
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_DEBUG,
+ "deferring until link to remote HLR is up\n");
+ proxy_defer_gsup_req(proxy, req);
+ return;
+ }
+
+ proxy_subscr_forward_to_remote_hlr_resolved(proxy, proxy_subscr, remote_hlr, req);
+}
+
+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..42bf700
--- /dev/null
+++ b/src/remote_hlr.c
@@ -0,0 +1,210 @@
+/* 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;
+}
+
+static bool remote_hlr_up_yield(struct proxy *proxy, const struct proxy_subscr *proxy_subscr, void *data)
+{
+ struct remote_hlr *remote_hlr = data;
+ proxy_subscr_remote_hlr_up(proxy, proxy_subscr, remote_hlr);
+ return true;
+}
+
+static bool remote_hlr_up_down(struct osmo_gsup_client *gsupc, bool up)
+{
+ struct remote_hlr *remote_hlr = gsupc->data;
+ 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");
+ proxy_subscrs_get_by_remote_hlr(g_hlr->gs->proxy, &remote_hlr->addr, remote_hlr_up_yield, remote_hlr);
+ return true;
+}
+
+struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create)
+{
+ struct remote_hlr *rh;
+ struct osmo_gsup_client_config cfg;
+
+ llist_for_each_entry(rh, &remote_hlrs, entry) {
+ if (!osmo_sockaddr_str_cmp(&rh->addr, addr))
+ return rh;
+ }
+
+ if (!create)
+ 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),
+ };
+ 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);
+ return NULL;
+ }
+ rh->gsupc->data = rh;
+ llist_add(&rh->entry, &remote_hlrs);
+ 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)
+{
+ 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 = 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);
+}
diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty
index ee351e3..fddffb2 100644
--- a/tests/test_nodes.vty
+++ b/tests/test_nodes.vty
@@ -61,10 +61,12 @@ OsmoHLR(config-hlr)# list
OsmoHLR(config-hlr)# gsup
OsmoHLR(config-hlr-gsup)# ?
...
- bind Listen/Bind related socket option
+ bind Listen/Bind related socket option
+ ipa-name Set the IPA name of this HLR, for proxying to remote HLRs
OsmoHLR(config-hlr-gsup)# list
...
bind ip A.B.C.D
+ ipa-name NAME
OsmoHLR(config-hlr-gsup)# exit
OsmoHLR(config-hlr)# exit
@@ -90,6 +92,7 @@ log stderr
logging level ss info
logging level mslookup notice
logging level lu notice
+ logging level dgsm notice
...
hlr
store-imei
@@ -99,3 +102,336 @@ hlr
ussd route prefix *#100# internal own-msisdn
ussd route prefix *#101# internal own-imsi
end
+
+OsmoHLR# configure terminal
+
+OsmoHLR(config)# mslookup
+OsmoHLR(config-mslookup)# ?
+...
+ mdns Multicast DNS related configuration
+ no Negate a command or set its defaults
+ server Enable and configure Distributed GSM mslookup server
+ client Enable and configure Distributed GSM mslookup client
+OsmoHLR(config-mslookup)# list
+...
+ mdns bind [IP] [<1-65535>]
+ mdns domain-suffix DOMAIN_SUFFIX
+ no mdns
+ server
+ no server
+ client
+ no client
+
+OsmoHLR(config-mslookup)# ?
+...
+ mdns Multicast DNS related configuration
+ no Negate a command or set its defaults
+ server Enable and configure Distributed GSM mslookup server
+ client Enable and configure Distributed GSM mslookup client
+OsmoHLR(config-mslookup)# no?
+ no Negate a command or set its defaults
+OsmoHLR(config-mslookup)# no ?
+ mdns Disable both server and client for mDNS mslookup
+ server Disable Distributed GSM mslookup server
+ client Disable Distributed GSM mslookup client
+OsmoHLR(config-mslookup)# mdns ?
+ bind Convenience shortcut: enable and configure both server and client for mDNS mslookup
+ domain-suffix mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
+OsmoHLR(config-mslookup)# mdns bind ?
+ [IP] multicast IPv4 address like 239.192.23.42 or IPv6 address like ff08::23:42
+OsmoHLR(config-mslookup)# mdns bind 1.2.3.4 ?
+ [<1-65535>] mDNS UDP Port number
+OsmoHLR(config-mslookup)# mdns domain-suffix ?
+ DOMAIN_SUFFIX mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
+
+OsmoHLR(config-mslookup)# server
+OsmoHLR(config-mslookup-server)# ?
+...
+ mdns Multicast DNS related configuration
+ no Negate a command or set its defaults
+ service Configure addresses of local services, as sent in replies to remote mslookup requests.
+ msc Configure services for individual local MSCs
+OsmoHLR(config-mslookup-server)# list
+...
+ mdns bind [IP] [<1-65535>]
+ mdns domain-suffix DOMAIN_SUFFIX
+ no mdns
+ service NAME at IP <1-65535>
+ no service NAME
+ no service NAME at IP <1-65535>
+ msc .UNIT_NAME
+
+OsmoHLR(config-mslookup-server)# mdns ?
+ bind Configure where the mDNS server listens for mslookup requests
+ domain-suffix mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
+OsmoHLR(config-mslookup-server)# mdns bind ?
+ [IP] multicast IPv4 address like 239.192.23.42 or IPv6 address like ff08::23:42
+OsmoHLR(config-mslookup-server)# mdns bind 1.2.3.4 ?
+ [<1-65535>] mDNS UDP Port number
+
+OsmoHLR(config-mslookup-server)# service?
+ service Configure addresses of local services, as sent in replies to remote mslookup requests.
+OsmoHLR(config-mslookup-server)# service ?
+ NAME mslookup service name, e.g. sip.voice or smpp.sms
+OsmoHLR(config-mslookup-server)# service foo ?
+ at at
+OsmoHLR(config-mslookup-server)# service foo at ?
+ IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
+OsmoHLR(config-mslookup-server)# service foo at 1.2.3.4 ?
+ <1-65535> Service-specific port number
+
+OsmoHLR(config-mslookup-server)# no ?
+ mdns Disable server for mDNS mslookup (do not answer remote requests)
+ service Remove one or more service address entries
+OsmoHLR(config-mslookup-server)# no service ?
+ NAME mslookup service name, e.g. sip.voice or smpp.sms
+OsmoHLR(config-mslookup-server)# no service foo ?
+ at at
+ <cr>
+OsmoHLR(config-mslookup-server)# no service foo at ?
+ IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
+OsmoHLR(config-mslookup-server)# no service foo at 1.2.3.4 ?
+ <1-65535> Service-specific port number
+
+OsmoHLR(config-mslookup-server)# msc?
+ msc Configure services for individual local MSCs
+OsmoHLR(config-mslookup-server)# msc ?
+ UNIT_NAME IPA Unit Name of the local MSC to configure
+
+OsmoHLR(config-mslookup-server)# msc MSC-1
+OsmoHLR(config-mslookup-server-msc)# ?
+...
+ service Configure addresses of local services, as sent in replies to remote mslookup requests.
+ no Negate a command or set its defaults
+OsmoHLR(config-mslookup-server-msc)# list
+...
+ service NAME at IP <1-65535>
+ no service NAME
+ no service NAME at IP <1-65535>
+
+OsmoHLR(config-mslookup-server-msc)# service?
+ service Configure addresses of local services, as sent in replies to remote mslookup requests.
+OsmoHLR(config-mslookup-server-msc)# service ?
+ NAME mslookup service name, e.g. sip.voice or smpp.sms
+OsmoHLR(config-mslookup-server-msc)# service foo ?
+ at at
+OsmoHLR(config-mslookup-server-msc)# service foo at ?
+ IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
+OsmoHLR(config-mslookup-server-msc)# service foo at 1.2.3.4 ?
+ <1-65535> Service-specific port number
+
+OsmoHLR(config-mslookup-server-msc)# no ?
+ service Remove one or more service address entries
+OsmoHLR(config-mslookup-server-msc)# no service ?
+ NAME mslookup service name, e.g. sip.voice or smpp.sms
+OsmoHLR(config-mslookup-server-msc)# no service foo ?
+ at at
+ <cr>
+OsmoHLR(config-mslookup-server-msc)# no service foo at ?
+ IP IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1
+OsmoHLR(config-mslookup-server-msc)# no service foo at 1.2.3.4 ?
+ <1-65535> Service-specific port number
+
+OsmoHLR(config-mslookup-server-msc)# exit
+OsmoHLR(config-mslookup-server)# exit
+OsmoHLR(config-mslookup)# client
+OsmoHLR(config-mslookup-client)# ?
+...
+ timeout How long should the mslookup client wait for remote responses before evaluating received results
+ mdns Multicast DNS related configuration
+ no Negate a command or set its defaults
+ gateway-proxy Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI
+OsmoHLR(config-mslookup-client)# list
+...
+ timeout <1-100000>
+ mdns bind [IP] [<1-65535>]
+ mdns domain-suffix DOMAIN_SUFFIX
+ no mdns
+ gateway-proxy IP [<1-65535>]
+ no gateway-proxy
+
+OsmoHLR(config-mslookup-client)# timeout?
+ timeout How long should the mslookup client wait for remote responses before evaluating received results
+OsmoHLR(config-mslookup-client)# timeout ?
+ <1-100000> timeout in milliseconds
+
+OsmoHLR(config-mslookup-client)# mdns?
+ mdns Multicast DNS related configuration
+OsmoHLR(config-mslookup-client)# mdns bind?
+ bind Enable mDNS client, and configure multicast address to send mDNS mslookup requests to
+OsmoHLR(config-mslookup-client)# mdns bind ?
+ [IP] multicast IPv4 address like 239.192.23.42 or IPv6 address like ff08::23:42
+OsmoHLR(config-mslookup-client)# mdns bind 1.2.3.4 ?
+ [<1-65535>] mDNS UDP Port number
+OsmoHLR(config-mslookup-client)# mdns domain-suffix?
+ domain-suffix mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
+OsmoHLR(config-mslookup-client)# mdns domain-suffix ?
+ DOMAIN_SUFFIX mDNS domain suffix (default: mdns.osmocom.org). This is appended and stripped from mDNS packets during encoding/decoding, so we don't collide with top-level domains administrated by IANA
+
+
+OsmoHLR(config-mslookup-client)# gateway-proxy?
+ gateway-proxy Configure a fixed IP address to send all GSUP requests for unknown IMSIs to, without invoking a lookup for IMSI
+OsmoHLR(config-mslookup-client)# gateway-proxy ?
+ IP IP address of the remote HLR
+OsmoHLR(config-mslookup-client)# gateway-proxy 1.2.3.4 ?
+ [<1-65535>] GSUP port number (omit for default 4222)
+
+OsmoHLR(config-mslookup-client)# no?
+ no Negate a command or set its defaults
+OsmoHLR(config-mslookup-client)# no ?
+ mdns Disable mDNS client, do not query remote services by mDNS
+ gateway-proxy Disable gateway proxy for GSUP with unknown IMSIs
+
+OsmoHLR(config-mslookup-client)# gateway-proxy ?
+ IP IP address of the remote HLR
+OsmoHLR(config-mslookup-client)# gateway-proxy 1.2.3.4 ?
+ [<1-65535>] GSUP port number (omit for default 4222)
+
+OsmoHLR(config-mslookup-client)# do show mslookup?
+ mslookup Distributed GSM / mslookup related information
+OsmoHLR(config-mslookup-client)# do show mslookup ?
+ services List configured service addresses as sent to remote mslookup requests
+
+OsmoHLR(config-mslookup-client)# gateway-proxy 1.2.3.4
+
+OsmoHLR(config-mslookup-client)# exit
+
+OsmoHLR(config-mslookup)# mdns bind
+OsmoHLR(config-mslookup)# server
+OsmoHLR(config-mslookup-server)# service qwert at 123.45.67.89 qwert
+% Unknown command.
+OsmoHLR(config-mslookup-server)# service qwert at qwert 1234
+% mslookup server: Invalid address for service qwert: qwert 1234
+OsmoHLR(config-mslookup-server)# service foo.bar at 123.45.67.89 1011
+OsmoHLR(config-mslookup-server)# service baz.bar at 121.31.41.5 1617
+OsmoHLR(config-mslookup-server)# service baz.bar at a:b:c::d 1819
+OsmoHLR(config-mslookup-server)# msc msc-901-70-23
+OsmoHLR(config-mslookup-server-msc)# service foo.bar at 76.54.32.10 1234
+OsmoHLR(config-mslookup-server-msc)# service baz.bar at 12.11.10.98 7654
+OsmoHLR(config-mslookup-server-msc)# service baz.bar at 999:999:999::999 9999
+OsmoHLR(config-mslookup-server-msc)# service baz.bar at dd:cc:bb::a 3210
+OsmoHLR(config-mslookup-server-msc)# exit
+OsmoHLR(config-mslookup-server)# msc msc-901-70-42
+OsmoHLR(config-mslookup-server-msc)# service foo.bar at 1.1.1.1 1111
+OsmoHLR(config-mslookup-server-msc)# service baz.bar at 2.2.2.2 2222
+OsmoHLR(config-mslookup-server-msc)# service baz.bar at 2222:2222:2222::2 2222
+OsmoHLR(config-mslookup-server-msc)# do show mslookup services
+Local GSUP HLR address returned in mslookup responses for local IMSIs: 127.0.0.1:4222
+service foo.bar at 123.45.67.89 1011
+service baz.bar at 121.31.41.5 1617
+service baz.bar at a:b:c::d 1819
+msc MSC-1
+msc msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+ service baz.bar at dd:cc:bb::a 3210
+msc msc-901-70-42
+ service foo.bar at 1.1.1.1 1111
+ service baz.bar at 2.2.2.2 2222
+ service baz.bar at 2222:2222:2222::2 2222
+
+OsmoHLR(config-mslookup-server-msc)# show running-config
+...
+mslookup
+ server
+ mdns bind 239.192.23.42 4266
+ service foo.bar at 123.45.67.89 1011
+ service baz.bar at 121.31.41.5 1617
+ service baz.bar at a:b:c::d 1819
+ msc MSC-1
+ msc msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+ service baz.bar at dd:cc:bb::a 3210
+ msc msc-901-70-42
+ service foo.bar at 1.1.1.1 1111
+ service baz.bar at 2.2.2.2 2222
+ service baz.bar at 2222:2222:2222::2 2222
+ client
+ gateway-proxy 1.2.3.4 4222
+ mdns to 239.192.23.42 4266
+...
+
+OsmoHLR(config-mslookup-server-msc)# no service baz.bar
+OsmoHLR(config-mslookup-server-msc)# no service asdf
+% mslookup server: cannot remove service 'asdf'
+OsmoHLR(config-mslookup-server-msc)# exit
+OsmoHLR(config-mslookup-server)# msc msc-901-70-23
+OsmoHLR(config-mslookup-server-msc)# no service baz.bar at dd:cc:bb::a 3210
+% mslookup server: cannot remove service 'baz.bar' to dd:cc:bb::a 3210
+OsmoHLR(config-mslookup-server-msc)# no service asdf at asdf asdf
+% Unknown command.
+OsmoHLR(config-mslookup-server-msc)# no service asdf at asdf 3210
+% mslookup server: Invalid address for 'no service' asdf: asdf 3210
+OsmoHLR(config-mslookup-server-msc)# no service asdf at dd:cc:bb::a 3210
+% mslookup server: cannot remove service 'asdf' to dd:cc:bb::a 3210
+OsmoHLR(config-mslookup-server-msc)# exit
+OsmoHLR(config-mslookup-server)# no service baz.bar at 2.2.2.2 2222
+% mslookup server: cannot remove service 'baz.bar' to 2.2.2.2 2222
+OsmoHLR(config-mslookup-server)# no service baz.bar at a:b:c::d 1819
+% mslookup server: cannot remove service 'baz.bar' to a:b:c::d 1819
+
+OsmoHLR(config-mslookup-server)# exit
+OsmoHLR(config-mslookup)# client
+OsmoHLR(config-mslookup-client)# no gateway-proxy
+
+OsmoHLR(config-mslookup-client)# do show mslookup services
+Local GSUP HLR address returned in mslookup responses for local IMSIs: 127.0.0.1:4222
+service foo.bar at 123.45.67.89 1011
+service baz.bar at 121.31.41.5 1617
+msc MSC-1
+msc msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+msc msc-901-70-42
+ service foo.bar at 1.1.1.1 1111
+
+OsmoHLR(config-mslookup-client)# show running-config
+...
+mslookup
+ server
+ mdns bind 239.192.23.42 4266
+ service foo.bar at 123.45.67.89 1011
+ service baz.bar at 121.31.41.5 1617
+ msc MSC-1
+ msc msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+ msc msc-901-70-42
+ service foo.bar at 1.1.1.1 1111
+ client
+ mdns to 239.192.23.42 4266
+...
+
+OsmoHLR(config-mslookup-client)# exit
+OsmoHLR(config-mslookup)# server
+OsmoHLR(config-mslookup-server)# service gsup.hlr at 23.42.17.11 4223
+OsmoHLR(config-mslookup-server)# do show mslookup services
+Local GSUP HLR address returned in mslookup responses for local IMSIs: 23.42.17.11:4223
+service foo.bar at 123.45.67.89 1011
+service baz.bar at 121.31.41.5 1617
+service gsup.hlr at 23.42.17.11 4223
+msc MSC-1
+msc msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+msc msc-901-70-42
+ service foo.bar at 1.1.1.1 1111
+
+OsmoHLR(config-mslookup-server)# show running-config
+...
+mslookup
+ server
+ mdns bind 239.192.23.42 4266
+ service foo.bar at 123.45.67.89 1011
+ service baz.bar at 121.31.41.5 1617
+ service gsup.hlr at 23.42.17.11 4223
+ msc MSC-1
+ msc msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+ msc msc-901-70-42
+ service foo.bar at 1.1.1.1 1111
+ client
+ mdns to 239.192.23.42 4266
+...