SMS over GSUP: implement vty config of SMSC routing
At the user-visible level (advanced settings menus on phones,
GSM 07.05 AT commands, SIM programming) each SMSC is identified
by a numeric address that looks like a phone number, originally
meant to be a Global Title. OsmoMSC passes these SMSC addresses
through as-is to MO-forwardSM.req GSUP message - however, SMSCs
that connect to OsmoHLR via GSUP identify themselves by their
IPA names instead. Hence we need a mapping mechanism in OsmoHLR
config.
To accommodate different styles of network design ranging from
strict recreation of classic GSM architecture to guest roaming
arrangements, a two-level configuration is implemented, modeled
after EUSE/USSD configuration: first one defines which SMSCs exist
as entities, identified only by their IPA names, and then one
defines which numeric SMSC address (in SM-RP-DA) should go to which
configured SMSC, with the additional possibility of a default route.
Related: OS#6135
Change-Id: I1624dcd9d22b4efca965ccdd1c74f0063a94a33c
diff --git a/src/Makefile.am b/src/Makefile.am
index 380e34a..6a3bb3f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -52,6 +52,7 @@
hlr_vty.c \
hlr_vty_subscr.c \
gsup_send.c \
+ hlr_sms.c \
hlr_ussd.c \
proxy.c \
dgsm.c \
diff --git a/src/hlr.c b/src/hlr.c
index 457850e..17acdab 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -750,8 +750,10 @@
g_hlr = talloc_zero(hlr_ctx, struct hlr);
INIT_LLIST_HEAD(&g_hlr->euse_list);
+ INIT_LLIST_HEAD(&g_hlr->smsc_list);
INIT_LLIST_HEAD(&g_hlr->ss_sessions);
INIT_LLIST_HEAD(&g_hlr->ussd_routes);
+ INIT_LLIST_HEAD(&g_hlr->smsc_routes);
INIT_LLIST_HEAD(&g_hlr->mslookup.server.local_site_services);
g_hlr->db_file_path = talloc_strdup(g_hlr, HLR_DEFAULT_DB_FILE_PATH);
g_hlr->mslookup.server.mdns.domain_suffix = talloc_strdup(g_hlr, OSMO_MDNS_DOMAIN_SUFFIX_DEFAULT);
diff --git a/src/hlr_sms.c b/src/hlr_sms.c
new file mode 100644
index 0000000..5866afa
--- /dev/null
+++ b/src/hlr_sms.c
@@ -0,0 +1,103 @@
+/* OsmoHLR SMS-over-GSUP routing implementation */
+
+/* Author: Mychaela N. Falconia <falcon@freecalypso.org>, 2023 - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ *
+ * Based on earlier unmerged work by Vadim Yanitskiy, 2019.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsup.h>
+
+#include <osmocom/hlr/hlr.h>
+#include <osmocom/hlr/hlr_sms.h>
+#include <osmocom/hlr/gsup_server.h>
+#include <osmocom/hlr/gsup_router.h>
+#include <osmocom/hlr/logging.h>
+#include <osmocom/hlr/db.h>
+
+/***********************************************************************
+ * core data structures expressing config from VTY
+ ***********************************************************************/
+
+struct hlr_smsc *smsc_find(struct hlr *hlr, const char *name)
+{
+ struct hlr_smsc *smsc;
+
+ llist_for_each_entry(smsc, &hlr->smsc_list, list) {
+ if (!strcmp(smsc->name, name))
+ return smsc;
+ }
+ return NULL;
+}
+
+struct hlr_smsc *smsc_alloc(struct hlr *hlr, const char *name)
+{
+ struct hlr_smsc *smsc = smsc_find(hlr, name);
+ if (smsc)
+ return NULL;
+
+ smsc = talloc_zero(hlr, struct hlr_smsc);
+ smsc->name = talloc_strdup(smsc, name);
+ smsc->hlr = hlr;
+ llist_add_tail(&smsc->list, &hlr->smsc_list);
+
+ return smsc;
+}
+
+void smsc_free(struct hlr_smsc *smsc)
+{
+ llist_del(&smsc->list);
+ talloc_free(smsc);
+}
+
+struct hlr_smsc_route *smsc_route_find(struct hlr *hlr, const char *num_addr)
+{
+ struct hlr_smsc_route *rt;
+
+ llist_for_each_entry(rt, &hlr->smsc_routes, list) {
+ if (!strcmp(rt->num_addr, num_addr))
+ return rt;
+ }
+ return NULL;
+}
+
+struct hlr_smsc_route *smsc_route_alloc(struct hlr *hlr, const char *num_addr,
+ struct hlr_smsc *smsc)
+{
+ struct hlr_smsc_route *rt;
+
+ if (smsc_route_find(hlr, num_addr))
+ return NULL;
+
+ rt = talloc_zero(hlr, struct hlr_smsc_route);
+ rt->num_addr = talloc_strdup(rt, num_addr);
+ rt->smsc = smsc;
+ llist_add_tail(&rt->list, &hlr->smsc_routes);
+
+ return rt;
+}
+
+void smsc_route_free(struct hlr_smsc_route *rt)
+{
+ llist_del(&rt->list);
+ talloc_free(rt);
+}
diff --git a/src/hlr_vty.c b/src/hlr_vty.c
index f8cf852..c4e99e2 100644
--- a/src/hlr_vty.c
+++ b/src/hlr_vty.c
@@ -44,6 +44,7 @@
#include <osmocom/hlr/hlr_vty.h>
#include <osmocom/hlr/hlr_vty_subscr.h>
#include <osmocom/hlr/hlr_ussd.h>
+#include <osmocom/hlr/hlr_sms.h>
#include <osmocom/hlr/gsup_server.h>
static const struct value_string gsm48_gmm_cause_vty_names[] = {
@@ -608,6 +609,160 @@
return CMD_SUCCESS;
}
+/***********************************************************************
+ * Routing of SM-RL to GSUP-attached SMSCs
+ ***********************************************************************/
+
+#define SMSC_STR "Configuration of GSUP routing to SMSCs\n"
+
+struct cmd_node smsc_node = {
+ SMSC_NODE,
+ "%s(config-hlr-smsc)# ",
+ 1,
+};
+
+DEFUN(cfg_smsc_entity, cfg_smsc_entity_cmd,
+ "smsc entity NAME",
+ SMSC_STR
+ "Configure a particular external SMSC\n"
+ "IPA name of the external SMSC\n")
+{
+ struct hlr_smsc *smsc;
+ const char *id = argv[0];
+
+ smsc = smsc_find(g_hlr, id);
+ if (!smsc) {
+ smsc = smsc_alloc(g_hlr, id);
+ if (!smsc)
+ return CMD_WARNING;
+ }
+ vty->index = smsc;
+ vty->index_sub = &smsc->description;
+ vty->node = SMSC_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_smsc_entity, cfg_no_smsc_entity_cmd,
+ "no smsc entity NAME",
+ NO_STR SMSC_STR "Remove a particular external SMSC\n"
+ "IPA name of the external SMSC\n")
+{
+ struct hlr_smsc *smsc = smsc_find(g_hlr, argv[0]);
+ if (!smsc) {
+ vty_out(vty, "%% Cannot remove non-existent SMSC %s%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (g_hlr->smsc_default == smsc) {
+ vty_out(vty,
+ "%% Cannot remove SMSC %s, it is the default route%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ smsc_free(smsc);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smsc_route, cfg_smsc_route_cmd,
+ "smsc route NUMBER NAME",
+ SMSC_STR
+ "Configure GSUP route to a particular SMSC\n"
+ "Numeric address of this SMSC, must match EF.SMSP programming in SIMs\n"
+ "IPA name of the external SMSC\n")
+{
+ struct hlr_smsc *smsc = smsc_find(g_hlr, argv[1]);
+ struct hlr_smsc_route *rt = smsc_route_find(g_hlr, argv[0]);
+ if (rt) {
+ vty_out(vty,
+ "%% Cannot add [another?] route for SMSC address %s%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!smsc) {
+ vty_out(vty, "%% Cannot find SMSC '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ smsc_route_alloc(g_hlr, argv[0], smsc);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_smsc_route, cfg_no_smsc_route_cmd,
+ "no smsc route NUMBER",
+ NO_STR SMSC_STR "Remove GSUP route to a particular SMSC\n"
+ "Numeric address of the SMSC\n")
+{
+ struct hlr_smsc_route *rt = smsc_route_find(g_hlr, argv[0]);
+ if (!rt) {
+ vty_out(vty, "%% Cannot find route for SMSC address %s%s",
+ argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ smsc_route_free(rt);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_smsc_defroute, cfg_smsc_defroute_cmd,
+ "smsc default-route NAME",
+ SMSC_STR
+ "Configure default SMSC route for unknown SMSC numeric addresses\n"
+ "IPA name of the external SMSC\n")
+{
+ struct hlr_smsc *smsc;
+
+ smsc = smsc_find(g_hlr, argv[0]);
+ if (!smsc) {
+ vty_out(vty, "%% Cannot find SMSC %s%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (g_hlr->smsc_default != smsc) {
+ vty_out(vty, "Switching default route from %s to %s%s",
+ g_hlr->smsc_default ? g_hlr->smsc_default->name : "<none>",
+ smsc->name, VTY_NEWLINE);
+ g_hlr->smsc_default = smsc;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_smsc_defroute, cfg_no_smsc_defroute_cmd,
+ "no smsc default-route",
+ NO_STR SMSC_STR
+ "Remove default SMSC route for unknown SMSC numeric addresses\n")
+{
+ g_hlr->smsc_default = NULL;
+
+ return CMD_SUCCESS;
+}
+
+static void dump_one_smsc(struct vty *vty, struct hlr_smsc *smsc)
+{
+ vty_out(vty, " smsc entity %s%s", smsc->name, VTY_NEWLINE);
+}
+
+static int config_write_smsc(struct vty *vty)
+{
+ struct hlr_smsc *smsc;
+ struct hlr_smsc_route *rt;
+
+ llist_for_each_entry(smsc, &g_hlr->smsc_list, list)
+ dump_one_smsc(vty, smsc);
+
+ llist_for_each_entry(rt, &g_hlr->smsc_routes, list) {
+ vty_out(vty, " smsc route %s %s%s", rt->num_addr,
+ rt->smsc->name, VTY_NEWLINE);
+ }
+
+ if (g_hlr->smsc_default)
+ vty_out(vty, " smsc default-route %s%s",
+ g_hlr->smsc_default->name, VTY_NEWLINE);
+
+ return 0;
+}
DEFUN(cfg_reject_cause, cfg_reject_cause_cmd,
"reject-cause TYPE CAUSE", "") /* Dynamically Generated */
@@ -771,6 +926,15 @@
install_element(HLR_NODE, &cfg_ussd_defaultroute_cmd);
install_element(HLR_NODE, &cfg_ussd_no_defaultroute_cmd);
install_element(HLR_NODE, &cfg_ncss_guard_timeout_cmd);
+
+ install_node(&smsc_node, config_write_smsc);
+ install_element(HLR_NODE, &cfg_smsc_entity_cmd);
+ install_element(HLR_NODE, &cfg_no_smsc_entity_cmd);
+ install_element(HLR_NODE, &cfg_smsc_route_cmd);
+ install_element(HLR_NODE, &cfg_no_smsc_route_cmd);
+ install_element(HLR_NODE, &cfg_smsc_defroute_cmd);
+ install_element(HLR_NODE, &cfg_no_smsc_defroute_cmd);
+
install_element(HLR_NODE, &cfg_reject_cause_cmd);
install_element(HLR_NODE, &cfg_store_imei_cmd);
install_element(HLR_NODE, &cfg_no_store_imei_cmd);