falconia submitted this change.
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
---
M include/osmocom/hlr/Makefile.am
M include/osmocom/hlr/hlr.h
A include/osmocom/hlr/hlr_sms.h
M include/osmocom/hlr/hlr_vty.h
M src/Makefile.am
M src/hlr.c
A src/hlr_sms.c
M src/hlr_vty.c
M tests/test_nodes.vty
9 files changed, 339 insertions(+), 0 deletions(-)
diff --git a/include/osmocom/hlr/Makefile.am b/include/osmocom/hlr/Makefile.am
index aceda4a..545ef6f 100644
--- a/include/osmocom/hlr/Makefile.am
+++ b/include/osmocom/hlr/Makefile.am
@@ -6,6 +6,7 @@
gsup_router.h \
gsup_server.h \
hlr.h \
+ hlr_sms.h \
hlr_ussd.h \
hlr_vty.h \
hlr_vty_subscr.h \
diff --git a/include/osmocom/hlr/hlr.h b/include/osmocom/hlr/hlr.h
index dc0c77e..278a85a 100644
--- a/include/osmocom/hlr/hlr.h
+++ b/include/osmocom/hlr/hlr.h
@@ -74,6 +74,10 @@
struct llist_head ss_sessions;
+ struct llist_head smsc_list;
+ struct llist_head smsc_routes;
+ struct hlr_smsc *smsc_default;
+
bool store_imei;
bool subscr_create_on_demand;
diff --git a/include/osmocom/hlr/hlr_sms.h b/include/osmocom/hlr/hlr_sms.h
new file mode 100644
index 0000000..0570aca
--- /dev/null
+++ b/include/osmocom/hlr/hlr_sms.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+
+struct hlr_smsc {
+ /* g_hlr->smsc_list */
+ struct llist_head list;
+ struct hlr *hlr;
+ /* name (must match the IPA ID tag) */
+ const char *name;
+ /* human-readable description */
+ const char *description;
+};
+
+struct hlr_smsc *smsc_find(struct hlr *hlr, const char *name);
+struct hlr_smsc *smsc_alloc(struct hlr *hlr, const char *name);
+void smsc_free(struct hlr_smsc *smsc);
+
+struct hlr_smsc_route {
+ /* g_hlr->smsc_routes */
+ struct llist_head list;
+ const char *num_addr;
+ struct hlr_smsc *smsc;
+};
+
+struct hlr_smsc_route *smsc_route_find(struct hlr *hlr, const char *num_addr);
+struct hlr_smsc_route *smsc_route_alloc(struct hlr *hlr, const char *num_addr,
+ struct hlr_smsc *smsc);
+void smsc_route_free(struct hlr_smsc_route *rt);
diff --git a/include/osmocom/hlr/hlr_vty.h b/include/osmocom/hlr/hlr_vty.h
index 43b6566..8601590 100644
--- a/include/osmocom/hlr/hlr_vty.h
+++ b/include/osmocom/hlr/hlr_vty.h
@@ -31,6 +31,7 @@
HLR_NODE = _LAST_OSMOVTY_NODE + 1,
GSUP_NODE,
EUSE_NODE,
+ SMSC_NODE,
MSLOOKUP_NODE,
MSLOOKUP_SERVER_NODE,
MSLOOKUP_SERVER_MSC_NODE,
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);
diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty
index 8c7e95d..4aea638 100644
--- a/tests/test_nodes.vty
+++ b/tests/test_nodes.vty
@@ -47,6 +47,7 @@
no Negate a command or set its defaults
ussd USSD Configuration
ncss-guard-timeout Set guard timer for NCSS (call independent SS) session activity
+ smsc Configuration of GSUP routing to SMSCs
reject-cause GSUP/GMM cause to be sent
store-imei Save the IMEI in the database when receiving Check IMEI requests. Note that an MSC does not necessarily send Check IMEI requests (for OsmoMSC, you may want to set 'check-imei-rqd 1').
subscriber-create-on-demand Make a new record when a subscriber is first seen.
@@ -63,6 +64,12 @@
ussd default-route external EUSE
no ussd default-route
ncss-guard-timeout <0-255>
+ smsc entity NAME
+ no smsc entity NAME
+ smsc route NUMBER NAME
+ no smsc route NUMBER
+ smsc default-route NAME
+ no smsc default-route
reject-cause (not-found|no-proxy) (imsi-unknown|illegal-ms|plmn-not-allowed|la-not-allowed|roaming-not-allowed|no-suitable-cell-in-la|net-fail|congestion|auth-unacceptable|proto-error-unspec)
store-imei
no store-imei
To view, visit change 34448. To unsubscribe, or for help writing mail filters, visit settings.