aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/hlr/Makefile.am3
-rw-r--r--include/osmocom/hlr/dgsm.h46
-rw-r--r--include/osmocom/hlr/gsup_server.h5
-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/mslookup_server.h4
-rw-r--r--include/osmocom/hlr/proxy.h95
-rw-r--r--include/osmocom/hlr/remote_hlr.h59
-rw-r--r--src/Makefile.am3
-rw-r--r--src/dgsm.c247
-rw-r--r--src/dgsm_vty.c200
-rw-r--r--src/gsup_server.c36
-rw-r--r--src/hlr.c21
-rw-r--r--src/hlr_vty.c19
-rw-r--r--src/logging.c6
-rw-r--r--src/mslookup_server.c97
-rw-r--r--src/proxy.c554
-rw-r--r--src/remote_hlr.c252
-rw-r--r--tests/gsup_server/Makefile.am1
-rw-r--r--tests/test_nodes.vty340
21 files changed, 2006 insertions, 9 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..cc8f3d2
--- /dev/null
+++ b/include/osmocom/hlr/dgsm.h
@@ -0,0 +1,46 @@
+/* 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/hlr/logging.h>
+#include <osmocom/gsupclient/cni_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 774f750..ce7556e 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;
};
@@ -71,3 +74,5 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
uint8_t *msisdn_enc, size_t msisdn_enc_size,
uint8_t *apn_buf, size_t apn_buf_size,
enum osmo_gsup_cn_domain cn_domain);
+int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_cni_peer_id *to_peer,
+ struct osmo_gsup_req *req, struct osmo_gsup_message *modified_gsup);
diff --git a/include/osmocom/hlr/hlr.h b/include/osmocom/hlr/hlr.h
index 8f26704..e8df5cd 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;
@@ -85,6 +87,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/mslookup_server.h b/include/osmocom/hlr/mslookup_server.h
index 440c451..7c80f2a 100644
--- a/include/osmocom/hlr/mslookup_server.h
+++ b/include/osmocom/hlr/mslookup_server.h
@@ -66,3 +66,7 @@ struct mslookup_server_msc_cfg *mslookup_server_msc_get(const struct osmo_ipa_na
const struct mslookup_service_host *mslookup_server_get_local_gsup_addr();
void mslookup_server_rx(const struct osmo_mslookup_query *query,
struct osmo_mslookup_result *result);
+
+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);
diff --git a/include/osmocom/hlr/proxy.h b/include/osmocom/hlr/proxy.h
new file mode 100644
index 0000000..0169038
--- /dev/null
+++ b/include/osmocom/hlr/proxy.h
@@ -0,0 +1,95 @@
+/* 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/cni_peer_id.h>
+#include <osmocom/hlr/timestamp.h>
+
+struct osmo_gsup_req;
+struct remote_hlr;
+
+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);
+
+int 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,
+ const struct osmo_sockaddr_str *remote_hlr_addr);
+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..6a4e8a1
--- /dev/null
+++ b/include/osmocom/hlr/remote_hlr.h
@@ -0,0 +1,59 @@
+/* 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 llist_head pending_up_callbacks;
+};
+
+/*! Receive a remote_hlr address when connecting succeeded, or remote_hlr == NULL on error.
+ * \param addr GSUP IP address and port for which the connection was requested.
+ * \param remote_hlr The connected remote_hlr ready for sending, or NULL if connecting failed.
+ * \param data Same a passed to remote_hlr_get_or_connect(). */
+typedef void (*remote_hlr_connect_result_cb_t)(const struct osmo_sockaddr_str *addr, struct remote_hlr *remote_hlr, void *data);
+
+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);
+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,
+ struct osmo_gsup_message *modified_gsup);
+
+bool remote_hlr_is_up(struct remote_hlr *remote_hlr);
diff --git a/src/Makefile.am b/src/Makefile.am
index ceaa093..09e9101 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..bfa5df8
--- /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/cni_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 facd2b7..6f29d3b 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 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 @@ 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_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 @@ 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);
@@ -322,6 +456,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 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 @@ 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);
@@ -378,5 +566,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_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 61b9257..15d2f83 100644
--- a/src/gsup_server.c
+++ b/src/gsup_server.c
@@ -502,3 +502,39 @@ int osmo_gsup_create_insert_subscriber_data_msg(struct osmo_gsup_message *gsup,
return 0;
}
+
+int osmo_gsup_forward_to_local_peer(struct osmo_gsup_server *server, const struct osmo_cni_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_CNI_PEER_ID_IPA_NAME) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
+ osmo_cni_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_CNI_PEER_ID_IPA_NAME) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
+ osmo_cni_peer_id_type_name(to_peer->type));
+ rc = -ENOTSUP;
+ goto routing_error;
+ }
+ LOG_GSUP_REQ(req, LOGL_INFO, "Forwarding to %s\n", osmo_cni_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 5e015b3..7b803e7 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/cni_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 @@ 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;
+ }
+
+ /* 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 @@ 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;
@@ -709,6 +723,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);
@@ -764,10 +781,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);
@@ -784,6 +804,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 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 @@ static bool subscriber_has_done_lu_here_hlr(const struct osmo_mslookup_query *qu
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 @@ static bool subscriber_has_done_lu_here(const struct osmo_mslookup_query *query,
* 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);
-
- /* Future: If proxy has a younger lu, replace. */
+ 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);
+
+ /* 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 @@ void mslookup_server_rx(const struct osmo_mslookup_query *query,
/* 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..1ae152c
--- /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_CNI_PEER_ID_IPA_NAME) {
+ LOG_PROXY_SUBSCR_MSG(proxy_subscr, &req->gsup, LOGL_ERROR,
+ "Unsupported GSUP peer id type: %s\n",
+ osmo_cni_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_cni_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_cni_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_cni_peer_id_to_str(&req->source_name),
+ osmo_cni_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_cni_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..00bfbb1
--- /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_CNI_PEER_ID_IPA_NAME) {
+ osmo_gsup_req_respond_err(req, GMM_CAUSE_NET_FAIL, "Unsupported GSUP peer id type: %s",
+ osmo_cni_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/gsup_server/Makefile.am b/tests/gsup_server/Makefile.am
index a32ddf9..34acd30 100644
--- a/tests/gsup_server/Makefile.am
+++ b/tests/gsup_server/Makefile.am
@@ -31,6 +31,7 @@ gsup_server_test_SOURCES = \
gsup_server_test_LDADD = \
$(top_srcdir)/src/gsup_server.c \
$(top_srcdir)/src/gsup_router.c \
+ $(top_srcdir)/src/gsup_send.c \
$(top_srcdir)/src/gsupclient/cni_peer_id.c \
$(top_srcdir)/src/gsupclient/gsup_req.c \
$(LIBOSMOCORE_LIBS) \
diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty
index ee351e3..d7d25c1 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,338 @@ 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 bind
+ 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 bind
+ service NAME at IP <1-65535>
+ no service NAME
+ no service NAME at IP <1-65535>
+ msc ipa-name .IPA_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 ?
+ ipa-name Identify locally connected MSC by IPA Unit Name
+OsmoHLR(config-mslookup-server)# msc ipa-name ?
+ IPA_NAME IPA Unit Name of the local MSC to configure
+
+OsmoHLR(config-mslookup-server)# msc ipa-name 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 bind
+ 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 ipa-name 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 ipa-name 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 ipa-name MSC-1
+msc ipa-name 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 ipa-name 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 bind 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 ipa-name 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 ipa-name MSC-1
+msc ipa-name msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+msc ipa-name 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 bind 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 ipa-name MSC-1
+msc ipa-name msc-901-70-23
+ service foo.bar at 76.54.32.10 1234
+ service baz.bar at 12.11.10.98 7654
+msc ipa-name 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 bind 239.192.23.42 4266
+...