diff options
author | Neels Hofmeyr <neels@hofmeyr.de> | 2019-10-30 02:07:48 +0100 |
---|---|---|
committer | Neels Hofmeyr <neels@hofmeyr.de> | 2019-11-11 05:31:59 +0100 |
commit | 3e142ab2308af886266b70c264c8b8aba2f8f721 (patch) | |
tree | f1efd49bc765b9b491e4cb7e3a408e49991eee93 | |
parent | 774d114635d3e1ff47b8dfcb7681e2f2cb1180d3 (diff) |
dgsm wip
Change-Id: I3c91d8e3cec7e4d87f9f56250908faa95c823925
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | src/Makefile.am | 11 | ||||
-rw-r--r-- | src/db.c | 7 | ||||
-rw-r--r-- | src/db.h | 4 | ||||
-rw-r--r-- | src/dgsm.c | 324 | ||||
-rw-r--r-- | src/dgsm.h | 65 | ||||
-rw-r--r-- | src/dgsm_vty.c | 307 | ||||
-rw-r--r-- | src/hlr.c | 16 | ||||
-rw-r--r-- | src/hlr.h | 19 | ||||
-rw-r--r-- | src/hlr_vty.c | 14 | ||||
-rw-r--r-- | src/hlr_vty.h | 4 | ||||
-rw-r--r-- | src/logging.c | 6 | ||||
-rw-r--r-- | src/logging.h | 1 | ||||
-rw-r--r-- | src/mslookup_server.c | 133 | ||||
-rw-r--r-- | src/mslookup_server.h | 9 | ||||
-rw-r--r-- | src/proxy.c | 114 | ||||
-rw-r--r-- | src/proxy.h | 18 | ||||
-rw-r--r-- | src/remote_hlr.c | 138 | ||||
-rw-r--r-- | src/remote_hlr.h | 19 | ||||
-rw-r--r-- | tests/test_nodes.vty | 39 |
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) @@ -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) @@ -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; + } +} @@ -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); @@ -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 + |