From ff7c7ea0857af84357ca9a05ebeb3c0304ff6a9c Mon Sep 17 00:00:00 2001 From: "Mychaela N. Falconia" Date: Thu, 21 Sep 2023 01:55:51 +0000 Subject: [PATCH] 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 --- include/osmocom/hlr/Makefile.am | 1 + include/osmocom/hlr/hlr.h | 4 + include/osmocom/hlr/hlr_sms.h | 29 ++++++ include/osmocom/hlr/hlr_vty.h | 1 + src/Makefile.am | 1 + src/hlr.c | 2 + src/hlr_sms.c | 103 ++++++++++++++++++++ src/hlr_vty.c | 164 ++++++++++++++++++++++++++++++++ tests/test_nodes.vty | 7 ++ 9 files changed, 312 insertions(+) create mode 100644 include/osmocom/hlr/hlr_sms.h create mode 100644 src/hlr_sms.c diff --git a/include/osmocom/hlr/Makefile.am b/include/osmocom/hlr/Makefile.am index aceda4a6..545ef6f9 100644 --- a/include/osmocom/hlr/Makefile.am +++ b/include/osmocom/hlr/Makefile.am @@ -6,6 +6,7 @@ noinst_HEADERS = \ 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 dc0c77e6..278a85af 100644 --- a/include/osmocom/hlr/hlr.h +++ b/include/osmocom/hlr/hlr.h @@ -74,6 +74,10 @@ struct hlr { 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 00000000..0570aca8 --- /dev/null +++ b/include/osmocom/hlr/hlr_sms.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +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 43b6566b..86015900 100644 --- a/include/osmocom/hlr/hlr_vty.h +++ b/include/osmocom/hlr/hlr_vty.h @@ -31,6 +31,7 @@ enum hlr_vty_node { 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 380e34a9..6a3bb3fe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -52,6 +52,7 @@ osmo_hlr_SOURCES = \ 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 457850e5..17acdabf 100644 --- a/src/hlr.c +++ b/src/hlr.c @@ -750,8 +750,10 @@ int main(int argc, char **argv) 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 00000000..5866afa1 --- /dev/null +++ b/src/hlr_sms.c @@ -0,0 +1,103 @@ +/* OsmoHLR SMS-over-GSUP routing implementation */ + +/* Author: Mychaela N. Falconia , 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 . + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +/*********************************************************************** + * 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 f8cf852e..c4e99e21 100644 --- a/src/hlr_vty.c +++ b/src/hlr_vty.c @@ -44,6 +44,7 @@ #include #include #include +#include #include static const struct value_string gsm48_gmm_cause_vty_names[] = { @@ -608,6 +609,160 @@ DEFUN(cfg_ncss_guard_timeout, cfg_ncss_guard_timeout_cmd, 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 : "", + 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 @@ void hlr_vty_init(void *hlr_ctx) 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 8c7e95d6..4aea6389 100644 --- a/tests/test_nodes.vty +++ b/tests/test_nodes.vty @@ -47,6 +47,7 @@ OsmoHLR(config-hlr)# ? 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 @@ OsmoHLR(config-hlr)# list 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