aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2019-10-30 02:07:48 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2019-11-11 05:31:59 +0100
commit3e142ab2308af886266b70c264c8b8aba2f8f721 (patch)
treef1efd49bc765b9b491e4cb7e3a408e49991eee93
parent774d114635d3e1ff47b8dfcb7681e2f2cb1180d3 (diff)
dgsm wip
-rw-r--r--configure.ac1
-rw-r--r--src/Makefile.am11
-rw-r--r--src/db.c7
-rw-r--r--src/db.h4
-rw-r--r--src/dgsm.c324
-rw-r--r--src/dgsm.h65
-rw-r--r--src/dgsm_vty.c307
-rw-r--r--src/hlr.c16
-rw-r--r--src/hlr.h19
-rw-r--r--src/hlr_vty.c14
-rw-r--r--src/hlr_vty.h4
-rw-r--r--src/logging.c6
-rw-r--r--src/logging.h1
-rw-r--r--src/mslookup_server.c133
-rw-r--r--src/mslookup_server.h9
-rw-r--r--src/proxy.c114
-rw-r--r--src/proxy.h18
-rw-r--r--src/remote_hlr.c138
-rw-r--r--src/remote_hlr.h19
-rw-r--r--tests/test_nodes.vty39
20 files changed, 1248 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index ca78f38..5089958 100644
--- a/configure.ac
+++ b/configure.ac
@@ -38,6 +38,7 @@ PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.2.0)
+PKG_CHECK_MODULES(LIBOSMOMSLOOKUP, libosmomslookup >= 1.2.0)
PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.6.0)
PKG_CHECK_MODULES(SQLITE3, sqlite3)
diff --git a/src/Makefile.am b/src/Makefile.am
index a042e4e..694a08e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -6,6 +6,7 @@ AM_CFLAGS = \
$(LIBOSMOGSM_CFLAGS) \
$(LIBOSMOVTY_CFLAGS) \
$(LIBOSMOCTRL_CFLAGS) \
+ $(LIBOSMOMSLOOKUP_CFLAGS) \
$(LIBOSMOABIS_CFLAGS) \
$(SQLITE3_CFLAGS) \
$(NULL)
@@ -37,6 +38,9 @@ noinst_HEADERS = \
hlr_vty_subscr.h \
hlr_ussd.h \
db_bootstrap.h \
+ proxy.h \
+ dgsm.h \
+ remote_hlr.h \
$(NULL)
bin_PROGRAMS = \
@@ -61,13 +65,20 @@ osmo_hlr_SOURCES = \
hlr_vty_subscr.c \
gsup_send.c \
hlr_ussd.c \
+ proxy.c \
+ dgsm.c \
+ dgsm_vty.c \
+ remote_hlr.c \
+ mslookup_server.c \
$(NULL)
osmo_hlr_LDADD = \
+ $(top_builddir)/src/gsupclient/libosmo-gsup-client.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOVTY_LIBS) \
$(LIBOSMOCTRL_LIBS) \
+ $(LIBOSMOMSLOOKUP_LIBS) \
$(LIBOSMOABIS_LIBS) \
$(SQLITE3_LIBS) \
$(NULL)
diff --git a/src/db.c b/src/db.c
index 4b8318a..4119f89 100644
--- a/src/db.c
+++ b/src/db.c
@@ -81,6 +81,13 @@ static const char *stmt_sql[] = {
[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
[DB_STMT_EXISTS_BY_IMSI] = "SELECT 1 FROM subscriber WHERE imsi = $imsi",
[DB_STMT_EXISTS_BY_MSISDN] = "SELECT 1 FROM subscriber WHERE msisdn = $msisdn",
+
+#if 0
+ [DB_STMT_PROXY_UPDATE] = "INSERT OR REPLACE INTO"
+ " proxy (imsi, remote_ip, remote_port)"
+ " VALUES ($imsi, $remote_ip, $remote_port)",
+ [DB_STMT_PROXY_GET_BY_IMSI] = "SELECT imsi, remote_ip, remote_port FROM proxy WHERE imsi = $imsi",
+#endif
};
static void sql3_error_log_cb(void *arg, int err_code, const char *msg)
diff --git a/src/db.h b/src/db.h
index 15d83de..17b77b2 100644
--- a/src/db.h
+++ b/src/db.h
@@ -30,6 +30,10 @@ enum stmt_idx {
DB_STMT_SET_LAST_LU_SEEN,
DB_STMT_EXISTS_BY_IMSI,
DB_STMT_EXISTS_BY_MSISDN,
+#if 0
+ DB_STMT_PROXY_UPDATE,
+ DB_STMT_PROXY_GET_BY_IMSI,
+#endif
_NUM_DB_STMT
};
diff --git a/src/dgsm.c b/src/dgsm.c
new file mode 100644
index 0000000..0482523
--- /dev/null
+++ b/src/dgsm.c
@@ -0,0 +1,324 @@
+#include <errno.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/mslookup/mslookup_client.h>
+#include <osmocom/mslookup/mslookup_client_dns.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include "logging.h"
+#include "hlr.h"
+#include "db.h"
+#include "gsup_router.h"
+#include "dgsm.h"
+#include "proxy.h"
+#include "remote_hlr.h"
+#include "mslookup_server.h"
+
+#define LOG_DGSM(imsi, level, fmt, args...) \
+ LOGP(DDGSM, level, "(IMSI-%s) " fmt, imsi, ##args)
+
+void *dgsm_ctx = NULL;
+struct dgsm_config dgsm_config = {
+ .server = {
+ .dns = {
+ .multicast_bind_addr = {
+ .ip = OSMO_MSLOOKUP_MDNS_IP4,
+ .port = OSMO_MSLOOKUP_MDNS_PORT,
+ },
+ },
+ },
+};
+
+
+struct dgsm_msc_config *dgsm_config_msc_get(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ bool create)
+{
+ struct dgsm_msc_config *msc;
+
+ if (!ipa_unit_name)
+ return NULL;
+
+ llist_for_each_entry(msc, &dgsm_config.server.msc_configs, entry) {
+ if (ipa_unit_name_len != msc->unit_name_len)
+ continue;
+ if (memcmp(ipa_unit_name, msc->unit_name, ipa_unit_name_len))
+ continue;
+ return msc;
+ }
+ if (!create)
+ return NULL;
+
+ msc = talloc_zero(dgsm_ctx, struct dgsm_msc_config);
+ OSMO_ASSERT(msc);
+ INIT_LLIST_HEAD(&msc->service_addrs);
+ msc->unit_name = talloc_memdup(msc, ipa_unit_name, ipa_unit_name_len);
+ OSMO_ASSERT(msc->unit_name);
+ msc->unit_name_len = ipa_unit_name_len;
+ return msc;
+}
+
+static struct dgsm_service_addr *dgsm_config_msc_service_get(struct dgsm_msc_config *msc, const char *service,
+ bool create)
+{
+ struct dgsm_service_addr *e;
+ llist_for_each_entry(e, &msc->service_addrs, entry) {
+ if (!strcmp(e->service, service))
+ return e;
+ }
+
+ if (!create)
+ return NULL;
+
+ e = talloc_zero(msc, struct dgsm_service_addr);
+ OSMO_ASSERT(e);
+ OSMO_STRLCPY_ARRAY(e->service, service);
+ llist_add_tail(&e->entry, &msc->service_addrs);
+ return e;
+}
+
+struct dgsm_service_addr *dgsm_config_service_get(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ const char *service)
+{
+ struct dgsm_msc_config *msc = dgsm_config_msc_get(ipa_unit_name, ipa_unit_name_len, false);
+ if (!msc)
+ return NULL;
+ return dgsm_config_msc_service_get(msc, service, false);
+}
+
+int dgsm_config_service_set(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ const char *service, const struct osmo_sockaddr_str *addr)
+{
+ struct dgsm_msc_config *msc;
+ struct dgsm_service_addr *e;
+
+ if (!service || !service[0]
+ || strlen(service) > OSMO_MSLOOKUP_SERVICE_MAXLEN)
+ return -EINVAL;
+ if (!addr || !osmo_sockaddr_str_is_nonzero(addr))
+ return -EINVAL;
+
+ msc = dgsm_config_msc_get(ipa_unit_name, ipa_unit_name_len, true);
+ if (!msc)
+ return -EINVAL;
+
+ e = dgsm_config_msc_service_get(msc, service, true);
+ if (!e)
+ return -EINVAL;
+
+ switch (addr->af) {
+ case AF_INET:
+ e->addr_v4 = *addr;
+ break;
+ case AF_INET6:
+ e->addr_v6 = *addr;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int dgsm_config_service_del(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ const char *service, const struct osmo_sockaddr_str *addr)
+{
+ struct dgsm_msc_config *msc;
+ struct dgsm_service_addr *e, *n;
+
+ msc = dgsm_config_msc_get(ipa_unit_name, ipa_unit_name_len, false);
+ if (!msc)
+ return -ENOENT;
+
+ llist_for_each_entry_safe(e, n, &msc->service_addrs, entry) {
+ if (service && strcmp(service, e->service))
+ continue;
+
+ if (addr) {
+ if (!osmo_sockaddr_str_cmp(addr, &e->addr_v4)) {
+ e->addr_v4 = (struct osmo_sockaddr_str){};
+ /* Removed one addr. If the other is still there, keep the entry. */
+ if (osmo_sockaddr_str_is_nonzero(&e->addr_v6))
+ continue;
+ } else if (!osmo_sockaddr_str_cmp(addr, &e->addr_v6)) {
+ e->addr_v6 = (struct osmo_sockaddr_str){};
+ /* Removed one addr. If the other is still there, keep the entry. */
+ if (osmo_sockaddr_str_is_nonzero(&e->addr_v4))
+ continue;
+ } else
+ /* No addr match, keep the entry. */
+ continue;
+ /* Addr matched and none is left. Delete. */
+ }
+ llist_del(&e->entry);
+ talloc_free(e);
+ }
+ return 0;
+}
+
+static void *dgsm_pending_messages_ctx = NULL;
+static struct osmo_mslookup_client *mslookup_client = NULL;
+
+struct pending_gsup_message {
+ struct llist_head entry;
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+ struct msgb *gsup;
+ struct timeval received_at;
+};
+static LLIST_HEAD(pending_gsup_messages);
+
+/* 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 defer_gsup_message(const struct osmo_gsup_message *gsup)
+{
+ struct pending_gsup_message *m;
+
+ m = talloc_zero(dgsm_pending_messages_ctx, struct pending_gsup_message);
+ OSMO_ASSERT(m);
+ timestamp_update(&m->received_at);
+ OSMO_STRLCPY_ARRAY(m->imsi, gsup->imsi);
+
+ /* Since osmo_gsup_message has a lot of dangling external pointers, the only way to defer the message is to
+ * store it encoded. */
+ m->gsup = osmo_gsup_msgb_alloc("GSUP proxy defer");
+ osmo_gsup_encode(m->gsup, gsup);
+
+ llist_add_tail(&m->entry, &pending_gsup_messages);
+}
+
+void dgsm_send_to_remote_hlr(const struct proxy_subscr *ps, const struct osmo_gsup_message *gsup)
+{
+ struct remote_hlr *remote_hlr;
+
+ if (!osmo_sockaddr_str_is_nonzero(&ps->remote_hlr)) {
+ /* We don't know the remote target yet. Still waiting for an MS lookup response. */
+ LOG_DGSM(gsup->imsi, LOGL_DEBUG, "GSUP Proxy: deferring until remote proxy is known: %s\n",
+ osmo_gsup_message_type_name(gsup->message_type));
+ defer_gsup_message(gsup);
+ return;
+ }
+
+ LOG_DGSM(gsup->imsi, LOGL_DEBUG, "GSUP Proxy: forwarding to " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&ps->remote_hlr), osmo_gsup_message_type_name(gsup->message_type));
+
+ remote_hlr = remote_hlr_get(&ps->remote_hlr, true);
+ if (!remote_hlr) {
+ LOG_DGSM(gsup->imsi, LOGL_ERROR, "Failed to establish connection to remote HLR " OSMO_SOCKADDR_STR_FMT
+ ", discarding GSUP: %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&ps->remote_hlr), osmo_gsup_message_type_name(gsup->message_type));
+ return;
+ }
+
+ remote_hlr_gsup_send(remote_hlr, gsup);
+}
+
+/* Return true when the message has been handled by D-GSM. */
+bool dgsm_check_forward_gsup_msg(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup)
+{
+ const struct proxy_subscr *ps;
+ struct proxy_subscr ps_new;
+ struct gsup_route *r;
+ struct osmo_gsup_message gsup_copy;
+
+ ps = proxy_subscr_get(gsup->imsi);
+ if (ps)
+ goto yes_we_are_proxying;
+
+ /* No proxy entry exists. If the IMSI is known in the local HLR, then we won't proxy. */
+ if (db_subscr_exists_by_imsi(g_hlr->dbc, gsup->imsi) == 0)
+ return false;
+
+ /* 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. */
+ ps_new = (struct proxy_subscr){};
+ OSMO_STRLCPY_ARRAY(ps_new.imsi, gsup->imsi);
+ proxy_subscr_update(&ps_new);
+ ps = &ps_new;
+
+yes_we_are_proxying:
+ OSMO_ASSERT(ps);
+
+ /* To forward to a remote HLR, we need to indicate the source MSC's name 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. */
+ r = gsup_route_find_by_conn(conn);
+ if (!r) {
+ /* The conn has not sent its IPA unit name yet, and hence we won't be able to proxy responses back from
+ * a remote HLR. Send GSUP error and indicate that this message has been handled. */
+ osmo_gsup_conn_send_err_reply(conn, gsup, GMM_CAUSE_NET_FAIL);
+ return true;
+ }
+
+ /* Be aware that osmo_gsup_message has a lot of external pointer references, so this is not a deep copy. */
+ gsup_copy = *gsup;
+ gsup_copy.source_name = r->addr;
+ gsup_copy.source_name_len = talloc_total_size(r->addr);
+
+ dgsm_send_to_remote_hlr(ps, &gsup_copy);
+ return true;
+}
+
+
+void dgsm_init(void *ctx)
+{
+ dgsm_ctx = talloc_named_const(ctx, 0, "dgsm");
+ dgsm_pending_messages_ctx = talloc_named_const(dgsm_ctx, 0, "dgsm_pending_messages");
+ INIT_LLIST_HEAD(&dgsm_config.server.msc_configs);
+}
+
+void dgsm_start(void *ctx)
+{
+ mslookup_client = osmo_mslookup_client_new(dgsm_ctx);
+ OSMO_ASSERT(mslookup_client);
+}
+
+void dgsm_dns_server_config_apply()
+{
+ /* Check whether to start/stop/restart DNS server */
+ bool should_run = dgsm_config.server.enable && dgsm_config.server.dns.enable;
+ bool should_stop = g_hlr->mslookup.server.dns
+ && (!should_run
+ || osmo_sockaddr_str_cmp(&dgsm_config.server.dns.multicast_bind_addr,
+ &g_hlr->mslookup.server.dns->multicast_bind_addr));
+
+ if (should_stop) {
+ osmo_mslookup_server_dns_stop(g_hlr->mslookup.server.dns);
+ LOGP(DDGSM, LOGL_ERROR, "Stopped MS Lookup DNS server\n");
+ }
+
+ if (should_run) {
+ g_hlr->mslookup.server.dns =
+ osmo_mslookup_server_dns_start(&dgsm_config.server.dns.multicast_bind_addr);
+ if (!g_hlr->mslookup.server.dns)
+ LOGP(DDGSM, LOGL_ERROR, "Failed to start MS Lookup DNS server\n");
+ else
+ LOGP(DDGSM, LOGL_ERROR, "Started MS Lookup DNS server on " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&g_hlr->mslookup.server.dns->multicast_bind_addr));
+ }
+}
+
+void dgsm_dns_client_config_apply()
+{
+ /* Check whether to start/stop/restart DNS client */
+ struct osmo_mslookup_client_method *dns_method = g_hlr->mslookup.client.dns;
+ const struct osmo_sockaddr_str *current_bind_addr = osmo_mslookup_client_method_dns_get_bind_addr(dns_method);
+
+ bool should_run = dgsm_config.client.enable && dgsm_config.client.dns.enable;
+ bool should_stop = dns_method &&
+ (!should_run
+ || osmo_sockaddr_str_cmp(&dgsm_config.client.dns.multicast_query_addr,
+ current_bind_addr));
+
+ if (should_stop)
+ osmo_mslookup_client_method_del(mslookup_client, dns_method);
+ if (should_run) {
+ if (osmo_mslookup_client_add_dns(mslookup_client,
+ dgsm_config.client.dns.multicast_query_addr.ip,
+ dgsm_config.client.dns.multicast_query_addr.port,
+ true))
+ LOGP(DDGSM, LOGL_ERROR, "Failed to start MS Lookup DNS client\n");
+ else
+ LOGP(DDGSM, LOGL_ERROR, "Started MS Lookup DNS client with " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(&dgsm_config.client.dns.multicast_query_addr));
+ }
+}
diff --git a/src/dgsm.h b/src/dgsm.h
new file mode 100644
index 0000000..22486c8
--- /dev/null
+++ b/src/dgsm.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <osmocom/mslookup/mslookup.h>
+#include "gsup_server.h"
+
+extern void *dgsm_ctx;
+
+struct dgsm_service_addr {
+ struct llist_head entry;
+ char service[OSMO_MSLOOKUP_SERVICE_MAXLEN+1];
+ struct osmo_sockaddr_str addr_v4;
+ struct osmo_sockaddr_str addr_v6;
+};
+
+struct dgsm_msc_config {
+ struct llist_head entry;
+ uint8_t *unit_name;
+ size_t unit_name_len;
+ struct llist_head service_addrs;
+};
+
+struct dgsm_config {
+ struct {
+ /* Whether to listen for incoming MS Lookup requests */
+ bool enable;
+
+ struct {
+ bool enable;
+ struct osmo_sockaddr_str multicast_bind_addr;
+ } dns;
+
+ struct llist_head msc_configs;
+ } server;
+
+ struct {
+ /* Whether to ask remote HLRs via MS Lookup if an IMSI is not known locally. */
+ bool enable;
+
+ struct {
+ /* Whether to use mDNS for IMSI MS Lookup */
+ bool enable;
+ struct osmo_sockaddr_str multicast_query_addr;
+ } dns;
+ } client;
+};
+
+extern struct dgsm_config dgsm_config;
+void dgsm_dns_server_config_apply();
+void dgsm_dns_client_config_apply();
+
+struct dgsm_service_addr *dgsm_config_service_get(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ const char *service);
+int dgsm_config_service_set(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ const char *service, const struct osmo_sockaddr_str *addr);
+int dgsm_config_service_del(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ const char *service, const struct osmo_sockaddr_str *addr);
+
+struct dgsm_msc_config *dgsm_config_msc_get(const uint8_t *ipa_unit_name, size_t ipa_unit_name_len,
+ bool create);
+
+void dgsm_init(void *ctx);
+bool dgsm_check_forward_gsup_msg(struct osmo_gsup_conn *conn, const struct osmo_gsup_message *gsup);
+
+void dgsm_vty_init();
+void dgsm_vty_go_parent_action(struct vty *vty);
diff --git a/src/dgsm_vty.c b/src/dgsm_vty.c
new file mode 100644
index 0000000..4567be9
--- /dev/null
+++ b/src/dgsm_vty.c
@@ -0,0 +1,307 @@
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include "hlr_vty.h"
+#include "dgsm.h"
+
+static struct dgsm_config dgsm_config_vty = {};
+
+struct cmd_node mslookup_node = {
+ MSLOOKUP_NODE,
+ "%s(config-mslookup)# ",
+ 1,
+};
+
+DEFUN(cfg_mslookup,
+ cfg_mslookup_cmd,
+ "mslookup",
+ "Configure Distributed GSM / multicast MS Lookup")
+{
+ vty->node = MSLOOKUP_NODE;
+ printf("%s vty->node = %d\n", __func__, vty->node);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_dns,
+ cfg_mslookup_dns_cmd,
+ "dns",
+ "Convenience shortcut: enable both server and client for DNS/mDNS MS Lookup with default config\n")
+{
+ dgsm_config_vty.server.enable = true;
+ dgsm_config_vty.server.dns.enable = true;
+ dgsm_config_vty.client.enable = true;
+ dgsm_config_vty.client.dns.enable = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_no_dns,
+ cfg_mslookup_no_dns_cmd,
+ "no dns",
+ NO_STR "Disable both server and client for DNS/mDNS MS Lookup\n")
+{
+ dgsm_config_vty.server.dns.enable = false;
+ dgsm_config_vty.client.dns.enable = false;
+ return CMD_SUCCESS;
+}
+
+struct cmd_node mslookup_server_node = {
+ MSLOOKUP_SERVER_NODE,
+ "%s(config-mslookup-server)# ",
+ 1,
+};
+
+DEFUN(cfg_mslookup_server,
+ cfg_mslookup_server_cmd,
+ "server",
+ "Enable and configure Distributed GSM / multicast MS Lookup server")
+{
+ vty->node = MSLOOKUP_SERVER_NODE;
+ dgsm_config_vty.server.enable = true;
+ printf("%s vty->node = %d\n", __func__, vty->node);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_no_server,
+ cfg_mslookup_no_server_cmd,
+ "no server",
+ NO_STR "Disable Distributed GSM / multicast MS Lookup server")
+{
+ dgsm_config_vty.server.enable = false;
+ return CMD_SUCCESS;
+}
+
+#define DNS_STR "Configure DNS/mDNS MS Lookup\n"
+#define DNS_BIND_STR DNS_STR "Configure where the DNS/mDNS server listens for MS Lookup requests\n"
+#define IP46_STR "IPv4 address like 1.2.3.4 or IPv6 address like a:b:c:d::1\n"
+#define PORT_STR "Port number\n"
+
+DEFUN(cfg_mslookup_server_dns_bind_multicast,
+ cfg_mslookup_server_dns_bind_multicast_cmd,
+ "dns bind multicast IP <1-65535>",
+ DNS_BIND_STR "Configure mDNS multicast listen address\n" IP46_STR PORT_STR)
+{
+ const char *ip_str = argv[1];
+ const char *port_str = argv[2];
+ struct osmo_sockaddr_str addr;
+ if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
+ || !osmo_sockaddr_str_is_nonzero(&addr)) {
+ vty_out(vty, "%% MS Lookup server: Invalid mDNS bind address: %s %s%s",
+ ip_str, port_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ dgsm_config_vty.server.dns.multicast_bind_addr = addr;
+ dgsm_config_vty.server.dns.enable = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_server_no_dns,
+ cfg_mslookup_server_no_dns_cmd,
+ "no dns",
+ NO_STR "Disable server for DNS/mDNS MS Lookup (do not answer remote requests)\n")
+{
+ dgsm_config_vty.server.dns.enable = false;
+ return CMD_SUCCESS;
+}
+
+struct cmd_node mslookup_server_msc_node = {
+ MSLOOKUP_SERVER_MSC_NODE,
+ "%s(config-mslookup-server-msc)# ",
+ 1,
+};
+
+DEFUN(cfg_mslookup_server_msc,
+ cfg_mslookup_server_msc_cmd,
+ "msc .UNIT_NAME",
+ "Configure services for individual local MSCs\n"
+ "IPA Unit Name of the local MSC to configure\n")
+{
+ const char *unit_name = argv_concat(argv, argc, 0);
+ struct dgsm_msc_config *msc = dgsm_config_msc_get((uint8_t*)unit_name, strlen(unit_name),
+ true);
+ if (!msc) {
+ vty_out(vty, "%% Error creating MSC %s%s",
+ osmo_quote_str(unit_name, -1), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty->node = MSLOOKUP_SERVER_MSC_NODE;
+ vty->index = msc;
+ printf("%s vty->node = %d\n", __func__, vty->node);
+ return CMD_SUCCESS;
+}
+
+#define SERVICE_NAME_STR \
+ "MS Lookup service name, e.g. " OSMO_MSLOOKUP_SERVICE_SIP " or " OSMO_MSLOOKUP_SERVICE_SMPP "\n"
+
+#define SERVICE_AND_NAME_STR \
+ "Configure addresses of local services, as sent in replies to remote MS Lookup requests.\n" \
+ SERVICE_NAME_STR
+
+
+DEFUN(cfg_mslookup_server_msc_service,
+ cfg_mslookup_server_msc_service_cmd,
+ "service NAME at IP <1-65535>",
+ SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
+{
+ /* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
+ * MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
+ struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
+ uint8_t *unit_name = msc ? msc->unit_name : NULL;
+ size_t unit_name_len = msc ? msc->unit_name_len : 0;
+ const char *service = argv[0];
+ const char *ip_str = argv[1];
+ const char *port_str = argv[2];
+ struct osmo_sockaddr_str addr;
+
+ if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
+ || !osmo_sockaddr_str_is_nonzero(&addr)) {
+ vty_out(vty, "%% MS Lookup server: Invalid address for service %s: %s %s%s",
+ service, ip_str, port_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (dgsm_config_service_set(unit_name, unit_name_len, service, &addr)) {
+ vty_out(vty, "%% MS Lookup server: Error setting service %s to %s %s%s",
+ service, ip_str, port_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+#define NO_SERVICE_AND_NAME_STR NO_STR "Remove one or more service address entries\n" SERVICE_NAME_STR
+
+DEFUN(cfg_mslookup_server_msc_no_service,
+ cfg_mslookup_server_msc_no_service_cmd,
+ "no service NAME",
+ NO_SERVICE_AND_NAME_STR)
+{
+ /* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
+ * MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
+ struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
+ uint8_t *unit_name = msc ? msc->unit_name : NULL;
+ size_t unit_name_len = msc ? msc->unit_name_len : 0;
+ const char *service = argv[0];
+
+ if (dgsm_config_service_del(unit_name, unit_name_len, service, NULL)) {
+ vty_out(vty, "%% MS Lookup server: Error removing service %s%s",
+ service, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_server_msc_no_service_addr,
+ cfg_mslookup_server_msc_no_service_addr_cmd,
+ "no service NAME at IP <1-65535>",
+ NO_SERVICE_AND_NAME_STR "at\n" IP46_STR PORT_STR)
+{
+ /* If this command is run on the 'server' node, it produces an empty unit name and serves as wildcard for all
+ * MSCs. If on a 'server' / 'msc' node, set services only for that MSC Unit Name. */
+ struct dgsm_msc_config *msc = (vty->node == MSLOOKUP_SERVER_MSC_NODE) ? vty->index : NULL;
+ uint8_t *unit_name = msc ? msc->unit_name : NULL;
+ size_t unit_name_len = msc ? msc->unit_name_len : 0;
+ const char *service = argv[0];
+ const char *ip_str = argv[1];
+ const char *port_str = argv[2];
+ struct osmo_sockaddr_str addr;
+
+ if (osmo_sockaddr_str_from_str(&addr, ip_str, atoi(port_str))
+ || !osmo_sockaddr_str_is_nonzero(&addr)) {
+ vty_out(vty, "%% MS Lookup server: Invalid address for 'no service' %s: %s %s%s",
+ service, ip_str, port_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (dgsm_config_service_del(unit_name, unit_name_len, service, &addr)) {
+ vty_out(vty, "%% MS Lookup server: Error removing service %s to %s %s%s",
+ service, ip_str, port_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ 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 / multicast MS Lookup client")
+{
+ vty->node = MSLOOKUP_CLIENT_NODE;
+ dgsm_config.client.enable = true;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_mslookup_no_client,
+ cfg_mslookup_no_client_cmd,
+ "no client",
+ NO_STR "Disable Distributed GSM / multicast MS Lookup client")
+{
+ dgsm_config.client.enable = false;
+ return CMD_SUCCESS;
+}
+
+int config_write_mslookup(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+int config_write_mslookup_server(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+int config_write_mslookup_server_msc(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+int config_write_mslookup_client(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+void dgsm_vty_init()
+{
+ install_element(CONFIG_NODE, &cfg_mslookup_cmd);
+
+ install_node(&mslookup_node, config_write_mslookup);
+ install_element(MSLOOKUP_NODE, &cfg_mslookup_dns_cmd);
+ install_element(MSLOOKUP_NODE, &cfg_mslookup_no_dns_cmd);
+ install_element(MSLOOKUP_NODE, &cfg_mslookup_server_cmd);
+ install_element(MSLOOKUP_NODE, &cfg_mslookup_no_server_cmd);
+
+ install_node(&mslookup_server_node, config_write_mslookup_server);
+ install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_dns_bind_multicast_cmd);
+ install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_no_dns_cmd);
+ install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_service_cmd);
+ install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_cmd);
+ install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_no_service_addr_cmd);
+ install_element(MSLOOKUP_SERVER_NODE, &cfg_mslookup_server_msc_cmd);
+
+ install_node(&mslookup_server_msc_node, config_write_mslookup_server_msc);
+ install_element(MSLOOKUP_SERVER_MSC_NODE, &cfg_mslookup_server_msc_service_cmd);
+ 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, config_write_mslookup_client);
+
+}
+
+void dgsm_vty_go_parent_action(struct vty *vty)
+{
+ /* Exiting 'mslookup' VTY node, apply new config. */
+ switch (vty->node) {
+ case MSLOOKUP_SERVER_NODE:
+ dgsm_dns_server_config_apply();
+ break;
+ case MSLOOKUP_CLIENT_NODE:
+ dgsm_dns_client_config_apply();
+ break;
+ }
+}
diff --git a/src/hlr.c b/src/hlr.c
index 0cc0448..600ef19 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/protocol/gsm_23_003.h>
+#include <osmocom/mslookup/mslookup_client.h>
#include "db.h"
#include "hlr.h"
@@ -47,6 +48,7 @@
#include "luop.h"
#include "hlr_vty.h"
#include "hlr_ussd.h"
+#include "dgsm.h"
struct hlr *g_hlr;
static void *hlr_ctx = NULL;
@@ -617,6 +619,13 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
if (gsup.destination_name_len)
return read_cb_forward(conn, msg, &gsup);
+ /* 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)
+ && dgsm_check_forward_gsup_msg(conn, &gsup))
+ goto cleanup_and_exit;
+
switch (gsup.message_type) {
/* requests sent to us */
case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
@@ -669,6 +678,8 @@ static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
osmo_gsup_message_type_name(gsup.message_type));
break;
}
+
+cleanup_and_exit:
msgb_free(msg);
return 0;
}
@@ -831,6 +842,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);
@@ -903,7 +917,7 @@ int main(int argc, char **argv)
}
while (!quit)
- osmo_select_main(0);
+ osmo_select_main_ctx(0);
osmo_gsup_server_destroy(g_hlr->gs);
db_close(g_hlr->dbc);
diff --git a/src/hlr.h b/src/hlr.h
index 18c4a1d..9bf3786 100644
--- a/src/hlr.h
+++ b/src/hlr.h
@@ -24,10 +24,13 @@
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/ipa.h>
#define HLR_DEFAULT_DB_FILE_PATH "hlr.db"
struct hlr_euse;
+struct osmo_gsup_conn;
+enum osmo_gsup_message_type;
struct hlr {
/* GSUP server pointer */
@@ -61,6 +64,22 @@ struct hlr {
/* Bitmask of DB_SUBSCR_FLAG_* */
uint8_t subscr_create_on_demand_flags;
unsigned int subscr_create_on_demand_rand_msisdn_len;
+
+ struct {
+ struct {
+ struct osmo_mslookup_server_dns *dns;
+ } server;
+
+ struct {
+ struct osmo_mslookup_client *client;
+
+ struct osmo_mslookup_client_method *dns;
+ } client;
+ } mslookup;
+
+ struct {
+ struct ipaccess_unit gsup_client_name;
+ } gsup_proxy;
};
extern struct hlr *g_hlr;
diff --git a/src/hlr_vty.c b/src/hlr_vty.c
index f7c5bc8..0c359f8 100644
--- a/src/hlr_vty.c
+++ b/src/hlr_vty.c
@@ -39,6 +39,7 @@
#include "hlr_vty_subscr.h"
#include "hlr_ussd.h"
#include "gsup_server.h"
+#include "dgsm.h"
struct cmd_node hlr_node = {
HLR_NODE,
@@ -396,6 +397,7 @@ DEFUN(cfg_no_subscr_create_on_demand, cfg_no_subscr_create_on_demand_cmd,
int hlr_vty_go_parent(struct vty *vty)
{
+ dgsm_vty_go_parent_action(vty);
switch (vty->node) {
case GSUP_NODE:
case EUSE_NODE:
@@ -403,6 +405,17 @@ int hlr_vty_go_parent(struct vty *vty)
vty->index = NULL;
vty->index_sub = NULL;
break;
+ case MSLOOKUP_CLIENT_NODE:
+ case MSLOOKUP_SERVER_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ vty->index_sub = NULL;
+ break;
+ case MSLOOKUP_SERVER_MSC_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ vty->index_sub = NULL;
+ break;
default:
case HLR_NODE:
vty->node = CONFIG_NODE;
@@ -462,4 +475,5 @@ void hlr_vty_init(void)
install_element(HLR_NODE, &cfg_no_subscr_create_on_demand_cmd);
hlr_vty_subscriber_init();
+ dgsm_vty_init();
}
diff --git a/src/hlr_vty.h b/src/hlr_vty.h
index 280b55a..9adca25 100644
--- a/src/hlr_vty.h
+++ b/src/hlr_vty.h
@@ -31,6 +31,10 @@ enum hlr_vty_node {
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
GSUP_NODE,
EUSE_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/src/logging.c b/src/logging.c
index 3fa2a69..31746e9 100644
--- a/src/logging.c
+++ b/src/logging.c
@@ -25,6 +25,12 @@ const struct log_info_cat hlr_log_info_cat[] = {
.color = "\033[1;34m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
+ [DDGSM] = {
+ .name = "DDGSM",
+ .description = "Distributed GSM: MS lookup and proxy",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
};
diff --git a/src/logging.h b/src/logging.h
index ed24075..0ad2ac3 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -8,6 +8,7 @@ enum {
DGSUP,
DAUC,
DSS,
+ DDGSM,
};
extern const struct log_info hlr_log_info;
diff --git a/src/mslookup_server.c b/src/mslookup_server.c
new file mode 100644
index 0000000..e2f3b75
--- /dev/null
+++ b/src/mslookup_server.c
@@ -0,0 +1,133 @@
+#include <string.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/mslookup/mslookup.h>
+#include "logging.h"
+#include "hlr.h"
+#include "db.h"
+#include "dgsm.h"
+#include "mslookup_server.h"
+
+static const struct osmo_mslookup_result not_found = {
+ .ip_v4.rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
+ .ip_v6.rc = OSMO_MSLOOKUP_RC_NOT_FOUND,
+ };
+
+static void set_result(struct osmo_mslookup_result_part *result,
+ const struct osmo_sockaddr_str *addr,
+ uint32_t age)
+{
+ if (!osmo_sockaddr_str_is_nonzero(addr)) {
+ result->rc = OSMO_MSLOOKUP_RC_NOT_FOUND;
+ return;
+ }
+ result->rc = OSMO_MSLOOKUP_RC_OK;
+ result->host = *addr;
+ result->age = age;
+}
+
+static void set_results(struct osmo_mslookup_result *result,
+ const struct dgsm_service_addr *service_addr,
+ uint32_t age)
+{
+ set_result(&result->ip_v4, &service_addr->addr_v4, age);
+ set_result(&result->ip_v6, &service_addr->addr_v6, age);
+}
+
+void mslookup_server_rx_hlr_gsup(const struct osmo_mslookup_query *query,
+ struct osmo_mslookup_result *result)
+{
+ struct dgsm_service_addr *addr;
+ int rc;
+ switch (query->id.type) {
+ case OSMO_MSLOOKUP_ID_IMSI:
+ rc = db_subscr_exists_by_imsi(g_hlr->dbc, query->id.imsi);
+ break;
+ case OSMO_MSLOOKUP_ID_MSISDN:
+ rc = db_subscr_exists_by_msisdn(g_hlr->dbc, query->id.msisdn);
+ break;
+ default:
+ LOGP(DDGSM, LOGL_ERROR, "Unknown mslookup ID type: %d\n", query->id.type);
+ *result = not_found;
+ return;
+ }
+
+ if (rc) {
+ LOGP(DDGSM, LOGL_DEBUG, "Does not exist in local HLR: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
+ *result = not_found;
+ return;
+ }
+
+ LOGP(DDGSM, LOGL_DEBUG, "Found in local HLR: %s\n",
+ osmo_mslookup_result_name_c(OTC_SELECT, query, NULL));
+
+ /* Find a HLR/GSUP service set for the server (no MSC unit name) */
+ addr = dgsm_config_service_get(NULL, 0, OSMO_MSLOOKUP_SERVICE_HLR_GSUP);
+ if (!addr) {
+ LOGP(DDGSM, LOGL_ERROR,
+ "Subscriber found, but no service '" OSMO_MSLOOKUP_SERVICE_HLR_GSUP "' configured,"
+ " cannot service HLR lookup request\n");
+ *result = not_found;
+ return;
+ }
+
+ set_results(result, addr, 0);
+}
+
+/* Determine whether the subscriber with the given ID has routed a Location Updating via this HLR as first hop -- either
+ * entirely here, or here first but proxying to a remote HLR. Do not return a match if the LU was registered here
+ * (because this is the home HLR) but the LU was routed via a closer HLR first.
+ * A proxy adds source_name IEs to forwarded GSUP requests that indicates the MSC where the subscriber is attached.
+ * So a) if the LU that was received at the home HLR contained a source_name, we know that the LU happened at a remote
+ * MSC. b) The source_name is stored as the vlr_number; hence if that vlr_number is not known locally, we know the LU
+ * happened at a remote MSC. (at the time of writing it is not yet clear whether we'll use a or b).
+ */
+bool subscriber_has_done_location_updating_here(const struct osmo_mslookup_id *id,
+ uint32_t *lu_age,
+ const uint8_t **lu_msc_unit_name,
+ size_t *lu_msc_unit_name_len)
+{
+ return false;
+}
+
+void mslookup_server_rx(const struct osmo_mslookup_query *query,
+ struct osmo_mslookup_result *result)
+{
+ const uint8_t *msc_unit_name;
+ size_t msc_unit_name_len;
+ const struct dgsm_service_addr *service_addr;
+ uint32_t age;
+
+ /* A request for a home HLR: answer exactly if this is the subscriber's home HLR, i.e. the IMSI is listed in the
+ * HLR database. */
+ if (strcmp(query->service, OSMO_MSLOOKUP_SERVICE_HLR_GSUP) != 0)
+ return mslookup_server_rx_hlr_gsup(query, result);
+
+ /* 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 MSC belonging to this
+ * HLR. Respond with whichever services are configured in the osmo-hlr.cfg. */
+ if (!subscriber_has_done_location_updating_here(&query->id, &age, &msc_unit_name, &msc_unit_name_len)) {
+ *result = not_found;
+ return;
+ }
+
+ /* We've detected a LU here. The MSC where the LU happened is stored in msc_unit_name, and the LU age is stored
+ * in 'age'. Figure out the address configured for that MSC and service name. */
+ service_addr = dgsm_config_service_get(msc_unit_name, msc_unit_name_len, query->service);
+ if (!service_addr) {
+ *result = not_found;
+ return;
+ }
+
+ set_results(result, service_addr, age);
+}
+
+struct osmo_mslookup_server_dns *osmo_mslookup_server_dns_start(const struct osmo_sockaddr_str *multicast_bind_addr)
+{
+ return NULL;
+}
+
+void osmo_mslookup_server_dns_stop(struct osmo_mslookup_server_dns *server)
+{
+}
+
diff --git a/src/mslookup_server.h b/src/mslookup_server.h
new file mode 100644
index 0000000..2b8d494
--- /dev/null
+++ b/src/mslookup_server.h
@@ -0,0 +1,9 @@
+#pragma once
+
+struct osmo_mslookup_server_dns {
+ bool running;
+ struct osmo_sockaddr_str multicast_bind_addr;
+};
+
+struct osmo_mslookup_server_dns *osmo_mslookup_server_dns_start(const struct osmo_sockaddr_str *multicast_bind_addr);
+void osmo_mslookup_server_dns_stop(struct osmo_mslookup_server_dns *server);
diff --git a/src/proxy.c b/src/proxy.c
new file mode 100644
index 0000000..7bcd080
--- /dev/null
+++ b/src/proxy.c
@@ -0,0 +1,114 @@
+
+#include <sys/time.h>
+#include <string.h>
+#include <talloc.h>
+#include <errno.h>
+
+#include <osmocom/core/timer.h>
+
+#include "proxy.h"
+
+/* 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;
+ struct timeval last_update;
+ struct proxy_subscr data;
+};
+
+static LLIST_HEAD(proxy_subscr_list);
+static void *proxy_ctx = NULL;
+
+/* How long to keep proxy entries without a refresh, in seconds. */
+static time_t proxy_fresh_time = 60 * 60;
+static time_t proxy_fresh_check_period = 60;
+static struct osmo_timer_list proxy_cleanup_timer;
+
+/* Central implementation to set a timestamp to the current time, in case we want to modify this in the future. */
+void timestamp_update(struct timeval *tv)
+{
+ osmo_gettimeofday(tv, NULL);
+}
+
+time_t timestamp_age(const struct timeval *last_update)
+{
+ struct timeval age;
+ struct timeval now;
+ timestamp_update(&now);
+ timersub(&now, last_update, &age);
+ return age.tv_sec;
+}
+
+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 struct proxy_subscr_listentry *_proxy_get(const char *imsi)
+{
+ struct proxy_subscr_listentry *e;
+ llist_for_each_entry(e, &proxy_subscr_list, entry) {
+ if (proxy_subscr_matches_imsi(&e->data, imsi))
+ return e;
+ }
+ return NULL;
+}
+
+const struct proxy_subscr *proxy_subscr_get(const char *imsi)
+{
+ struct proxy_subscr_listentry *e = _proxy_get(imsi);
+ if (!e)
+ return NULL;
+ return &e->data;
+}
+
+int proxy_subscr_update(const struct proxy_subscr *proxy_subscr)
+{
+ struct proxy_subscr_listentry *e = _proxy_get(proxy_subscr->imsi);
+ if (!e) {
+ /* Does not exist yet */
+ e = talloc_zero(proxy_ctx, 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(const char *imsi)
+{
+ struct proxy_subscr_listentry *e = _proxy_get(imsi);
+ if (!e)
+ return -ENOENT;
+ return _proxy_subscr_del(e);
+}
+
+/* Discard stale proxy entries. */
+static void proxy_cleanup(void *ignore)
+{
+ struct proxy_subscr_listentry *e, *n;
+ llist_for_each_entry_safe(e, n, &proxy_subscr_list, entry) {
+ if (timestamp_age(&e->last_update) <= proxy_fresh_time)
+ continue;
+ _proxy_subscr_del(e);
+ }
+ osmo_timer_schedule(&proxy_cleanup_timer, proxy_fresh_check_period, 0);
+}
+
+void proxy_init(void *ctx)
+{
+ proxy_ctx = ctx;
+ osmo_timer_setup(&proxy_cleanup_timer, &proxy_cleanup, NULL);
+ /* Invoke to trigger the first timer schedule */
+ proxy_cleanup(NULL);
+}
+
diff --git a/src/proxy.h b/src/proxy.h
new file mode 100644
index 0000000..91d433d
--- /dev/null
+++ b/src/proxy.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <time.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/core/sockaddr_str.h>
+
+struct proxy_subscr {
+ char imsi[GSM23003_IMSI_MAX_DIGITS+1];
+ struct osmo_sockaddr_str remote_hlr;
+};
+
+void proxy_init(void *ctx);
+const struct proxy_subscr *proxy_subscr_get(const char *imsi);
+int proxy_subscr_update(const struct proxy_subscr *proxy_subscr);
+int proxy_subscr_del(const char *imsi);
+
+void timestamp_update(struct timeval *timestamp);
+time_t timestamp_age(const struct timeval *timestamp);
diff --git a/src/remote_hlr.c b/src/remote_hlr.c
new file mode 100644
index 0000000..600b037
--- /dev/null
+++ b/src/remote_hlr.c
@@ -0,0 +1,138 @@
+#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
+#include <osmocom/gsm/gsup.h>
+#include <osmocom/abis/ipa.h>
+#include <osmocom/gsupclient/gsup_client.h>
+#include "logging.h"
+#include "hlr.h"
+#include "gsup_server.h"
+#include "gsup_router.h"
+#include "dgsm.h"
+#include "remote_hlr.h"
+
+static LLIST_HEAD(remote_hlrs);
+
+#define LOG_GSUPC(gsupc, level, fmt, args...) \
+ LOGP(DDGSM, level, "HLR Proxy: GSUP from %s:%u: " fmt, (gsupc)->link->addr, (gsupc)->link->port, ##args)
+
+#define LOG_GSUP_MSG(gsupc, gsup_msg, level, fmt, args...) \
+ LOG_GSUPC(gsupc, level, "%s: " fmt, osmo_gsup_message_type_name((gsup_msg)->message_type), ##args)
+
+
+void remote_hlr_err_reply(struct osmo_gsup_client *gsupc, 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(gsupc, &gsup_reply))
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to send Error reply (imsi=%s)\n",
+ osmo_quote_str(gsup_orig->imsi, -1));
+}
+
+static int remote_hlr_rx(struct osmo_gsup_client *gsupc, struct msgb *msg)
+{
+ struct osmo_gsup_message gsup;
+ struct osmo_gsup_conn *msc_conn;
+ int rc;
+
+ rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
+ if (rc < 0) {
+ LOG_GSUPC(gsupc, 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 (!gsup.imsi[0]) {
+ LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Failed to decode GSUP message: missing IMSI\n");
+ remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
+ return -GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ /* Since this is a proxy link to a remote osmo-msc, we are acting on behalf of a local MSC, and need to know the
+ * routing name of that local MSC. We have sent it to the remote HLR as source_name, and we're required to get
+ * it back as destination_name. */
+ if (!gsup.destination_name || !gsup.destination_name_len) {
+ LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "message lacks Destination Name IE, cannot route to MSC.\n");
+ remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_INV_MAND_INFO);
+ return -GMM_CAUSE_INV_MAND_INFO;
+ }
+
+ /* Route to MSC that we're proxying for */
+ msc_conn = gsup_route_find(g_hlr->gs, gsup.destination_name, gsup.destination_name_len);
+ if (!msc_conn) {
+ LOG_GSUP_MSG(gsupc, &gsup, LOGL_ERROR, "Destination MSC unreachable: %s\n",
+ osmo_quote_str((char*)gsup.destination_name, gsup.destination_name_len));
+ remote_hlr_err_reply(gsupc, &gsup, GMM_CAUSE_MSC_TEMP_NOTREACH);
+ return -GMM_CAUSE_MSC_TEMP_NOTREACH;
+ }
+
+ /* The outgoing message needs to be a separate msgb, because osmo_gsup_conn_send() takes ownership of it. */
+ return osmo_gsup_conn_send(msc_conn, msgb_copy(msg, "GSUP proxy to MSC"));
+}
+
+struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create)
+{
+ struct remote_hlr *rh;
+
+ llist_for_each_entry(rh, &remote_hlrs, entry) {
+ if (!osmo_sockaddr_str_cmp(&rh->addr, addr))
+ return rh;
+ }
+
+ if (!create)
+ return NULL;
+
+ /* Doesn't exist yet, create a GSUP client to remote HLR. */
+ rh = talloc_zero(dgsm_ctx, struct remote_hlr);
+ *rh = (struct remote_hlr){
+ .addr = *addr,
+ .gsupc = osmo_gsup_client_create2(rh, &g_hlr->gsup_proxy.gsup_client_name,
+ addr->ip, addr->port,
+ remote_hlr_rx,
+ NULL),
+ };
+ if (!rh->gsupc) {
+ talloc_free(rh);
+ return NULL;
+ }
+ return rh;
+}
+
+/* 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;
+}
+
+int remote_hlr_gsup_send(struct remote_hlr *remote_hlr, const struct osmo_gsup_message *gsup)
+{
+ struct msgb *msg = osmo_gsup_msgb_alloc("GSUP proxy to remote HLR");
+ osmo_gsup_encode(msg, gsup);
+ return remote_hlr_msgb_send(remote_hlr, msg);
+}
+
diff --git a/src/remote_hlr.h b/src/remote_hlr.h
new file mode 100644
index 0000000..331ef35
--- /dev/null
+++ b/src/remote_hlr.h
@@ -0,0 +1,19 @@
+#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 msgb;
+
+struct remote_hlr {
+ struct llist_head entry;
+ struct osmo_sockaddr_str addr;
+ struct osmo_gsup_client *gsupc;
+};
+
+struct remote_hlr *remote_hlr_get(const struct osmo_sockaddr_str *addr, bool create);
+int remote_hlr_msgb_send(struct remote_hlr *remote_hlr, struct msgb *msg);
+int remote_hlr_gsup_send(struct remote_hlr *remote_hlr, const struct osmo_gsup_message *gsup);
diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty
index 2be9617..844b1fd 100644
--- a/tests/test_nodes.vty
+++ b/tests/test_nodes.vty
@@ -52,6 +52,7 @@ OsmoHLR(config)# list
end
...
hlr
+ mslookup
OsmoHLR(config)# hlr
OsmoHLR(config-hlr)# list
@@ -113,3 +114,41 @@ 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)# list
+...
+ dns
+ no dns
+ server
+ no server
+ client
+ no client
+
+OsmoHLR(config-mslookup)# server
+OsmoHLR(config-mslookup-server)# list
+...
+ dns bind multicast IP <1-65535>
+ no dns
+ service NAME at IP <1-65535>
+ no service NAME
+ no service NAME at IP <1-65535>
+ msc .UNIT_NAME
+
+OsmoHLR(config-mslookup-server)# msc MSC-1
+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)# exit
+OsmoHLR(config-mslookup-server)# exit
+OsmoHLR(config-mslookup)# client
+OsmoHLR(config-mslookup-client)# list
+...
+ timeout <1-255>
+ dns to multicast IP <1-65535>
+ no dns
+