aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2019-11-20 03:35:37 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2020-04-30 19:22:24 +0200
commit76328bdc9165ef7355528c86d655d3b5086607d6 (patch)
tree920859c8d7fc4d7c1d5059d976681fcb190f3f17
parent407925dcab8bdff8e3d6c2cf4ed7b73b7d69141e (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.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
+...