diff options
Diffstat (limited to 'src')
70 files changed, 11265 insertions, 2663 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 4e7cea19b..746c80d0f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,20 +13,22 @@ AM_CFLAGS = \ $(COVERAGE_CFLAGS) \ $(NULL) -AM_LDFLAGS = \ - $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOGSM_LIBS) \ - $(COVERAGE_LDFLAGS) \ - $(NULL) - # Libraries SUBDIRS = \ libvlr \ libmsc \ $(NULL) +if BUILD_SMPP + +SUBDIRS += \ + libsmpputil \ + utils \ + $(NULL) + +endif + # Programs SUBDIRS += \ osmo-msc \ - utils \ $(NULL) diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am index d83489680..dff4b2db0 100644 --- a/src/libmsc/Makefile.am +++ b/src/libmsc/Makefile.am @@ -20,9 +20,6 @@ AM_CFLAGS = \ $(LIBOSMONETIF_CFLAGS) \ $(NULL) -noinst_HEADERS = \ - $(NULL) - noinst_LIBRARIES = \ libmsc.a \ $(NULL) @@ -30,6 +27,10 @@ noinst_LIBRARIES = \ libmsc_a_SOURCES = \ call_leg.c \ cell_id_list.c \ + codec_filter.c \ + codec_mapping.c \ + csd_bs.c \ + csd_filter.c \ sccp_ran.c \ msc_vty.c \ db.c \ @@ -54,6 +55,7 @@ libmsc_a_SOURCES = \ msc_t.c \ msc_t_remote.c \ msc_ho.c \ + msc_vgcs.c \ neighbor_ident.c \ neighbor_ident_vty.c \ paging.c \ @@ -64,14 +66,19 @@ libmsc_a_SOURCES = \ ran_peer.c \ rrlp.c \ rtp_stream.c \ + sdp_msg.c \ silent_call.c \ sms_queue.c \ + smsc_vty.c \ transaction.c \ + transaction_cc.c \ msc_net_init.c \ ctrl_commands.c \ sgs_iface.c \ sgs_server.c \ sgs_vty.c \ + asci_gcr.c \ + asci_vty.c \ $(NULL) if BUILD_IU @@ -79,16 +86,3 @@ libmsc_a_SOURCES += \ ran_msg_iu.c \ $(NULL) endif - -if BUILD_SMPP -noinst_HEADERS += \ - smpp_smsc.h \ - $(NULL) - -libmsc_a_SOURCES += \ - smpp_smsc.c \ - smpp_openbsc.c \ - smpp_vty.c \ - smpp_utils.c \ - $(NULL) -endif diff --git a/src/libmsc/asci_gcr.c b/src/libmsc/asci_gcr.c new file mode 100644 index 000000000..220815136 --- /dev/null +++ b/src/libmsc/asci_gcr.c @@ -0,0 +1,175 @@ +/* Group Call Register (GCR) */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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 <osmocom/msc/gsm_data.h> +#include <osmocom/msc/transaction.h> + +#include <osmocom/msc/asci_gcr.h> + +#define GCR_DEFAULT_TIMEOUT 60 + +static uint32_t pow10[9] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + +/* Add cell to BSS list. */ +struct gcr_cell *gcr_add_cell(struct gcr_bss *bss, uint16_t cell_id) +{ + struct gcr_cell *c; + + c = talloc_zero(bss, struct gcr_cell); + if (!c) + return NULL; + c->cell_id = cell_id; + llist_add_tail(&c->list, &bss->cell_list); + + return c; +} + +/* Find cell entry in BSS list. */ +struct gcr_cell *gcr_find_cell(struct gcr_bss *bss, uint16_t cell_id) +{ + struct gcr_cell *c; + + llist_for_each_entry(c, &bss->cell_list, list) { + if (c->cell_id == cell_id) + return c; + } + + return NULL; +} + +/* Remove cell entry from BSS list. */ +void gcr_rm_cell(struct gcr_bss *bss, uint16_t cell_id) +{ + struct gcr_cell *c = gcr_find_cell(bss, cell_id); + + if (c) { + llist_del(&c->list); + talloc_free(c); + } +} + +/* Add BSS to GCR list. */ +struct gcr_bss *gcr_add_bss(struct gcr *gcr, int pc) +{ + struct gcr_bss *b; + + b = talloc_zero(gcr, struct gcr_bss); + if (!b) + return NULL; + INIT_LLIST_HEAD(&b->cell_list); + b->pc = pc; + llist_add_tail(&b->list, &gcr->bss_list); + + return b; +} + +/* Find BSS entry in GCR list. */ +struct gcr_bss *gcr_find_bss(struct gcr *gcr, int pc) +{ + struct gcr_bss *b; + + llist_for_each_entry(b, &gcr->bss_list, list) { + if (b->pc == pc) + return b; + } + + return NULL; +} + +/* Remove BSS entry from GCR list. */ +void gcr_rm_bss(struct gcr *gcr, int pc) +{ + struct gcr_bss *b = gcr_find_bss(gcr, pc); + + if (b) { + /* All cell definitons will be removed, as they are attached to BSS. */ + llist_del(&b->list); + talloc_free(b); + } +} + +/* Create a new (empty) GCR list. */ +struct gcr *gcr_create(struct gsm_network *gsmnet, enum trans_type trans_type, const char *group_id) +{ + struct gcr *gcr; + + gcr = talloc_zero(gsmnet, struct gcr); + if (!gcr) + return NULL; + + INIT_LLIST_HEAD(&gcr->bss_list); + gcr->trans_type = trans_type; + gcr->timeout = GCR_DEFAULT_TIMEOUT; + gcr->mute_talker = true; + osmo_strlcpy(gcr->group_id, group_id, sizeof(gcr->group_id)); + llist_add_tail(&gcr->list, &gsmnet->asci.gcr_lists); + + return gcr; +} + +/* Destroy a GCR list. */ +void gcr_destroy(struct gcr *gcr) +{ + /* All BSS definitons will be removed, as they are attached to GCR. */ + llist_del(&gcr->list); + talloc_free(gcr); +} + +/* Find GCR list by group ID. */ +struct gcr *gcr_by_group_id(struct gsm_network *gsmnet, enum trans_type trans_type, const char *group_id) +{ + struct gcr *gcr; + + llist_for_each_entry(gcr, &gsmnet->asci.gcr_lists, list) { + if (gcr->trans_type == trans_type && !strcmp(gcr->group_id, group_id)) + return gcr; + } + + return NULL; +} + +/* Find GCR list by callref. */ +struct gcr *gcr_by_callref(struct gsm_network *gsmnet, enum trans_type trans_type, uint32_t callref) +{ + struct gcr *most_specific_gcr = NULL, *gcr; + int a, b; + size_t most_specific_len = 0, l; + + llist_for_each_entry(gcr, &gsmnet->asci.gcr_lists, list) { + /* Compare only the digits in Group ID with the digits in callref. + * callref is an integer. Only the remainder, based on Group ID length, is checked. */ + l = strlen(gcr->group_id); + a = atoi(gcr->group_id); + OSMO_ASSERT(l < ARRAY_SIZE(pow10)); + b = callref % pow10[l]; + if (gcr->trans_type == trans_type && a == b) { + /* Get most specific GROUP ID, no matter what order they are stored. */ + if (l > most_specific_len) { + most_specific_gcr = gcr; + most_specific_len = l; + } + } + } + + return most_specific_gcr; +} diff --git a/src/libmsc/asci_vty.c b/src/libmsc/asci_vty.c new file mode 100644 index 000000000..a138eda51 --- /dev/null +++ b/src/libmsc/asci_vty.c @@ -0,0 +1,434 @@ +/* GCR interface to VTY */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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 <osmocom/msc/vty.h> +#include <osmocom/msc/transaction.h> +#include <osmocom/msc/msc_vgcs.h> +#include <osmocom/msc/asci_vty.h> +#include <osmocom/msc/asci_gcr.h> + +static struct gsm_network *gsmnet; + +/*********************************************************************** + * ASCI Node + ***********************************************************************/ + +#define ASCI_STR "Advanced Speech Call Items\n" + +static void asci_disabled(struct vty *vty) +{ + vty_out(vty, "%%Advanced Speech Call Items are disabled.%s", VTY_NEWLINE); +} + +DEFUN(asci_call, asci_call_cmd, + "asci (initiate|terminate) (vgc|vbc) CALLREF", + ASCI_STR "Initiate a call\nTerminate a call\nVoice Group Call\nVoice Broadcast Call\nCall reference") +{ + struct gcr *gcr; + const char *error; + + if (!gsmnet->asci.enable) { + asci_disabled(vty); + return CMD_WARNING; + } + + gcr = gcr_by_group_id(gsmnet, (argv[1][1] == 'g') ? TRANS_GCC : TRANS_BCC, argv[2]); + if (!gcr) { + vty_out(vty, "%%Given call ref does not exist in GCR.%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (argv[0][0] == 'i') + error = vgcs_vty_initiate(gsmnet, gcr); + else + error = vgcs_vty_terminate(gsmnet, gcr); + if (error) { + vty_out(vty, "%%%s%s", error, VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(asci_show, asci_show_cmd, + "show asci calls", + SHOW_STR ASCI_STR "Show all Voice Group/Broadcast Calls") +{ + struct gsm_trans *trans; + const char *typestr; + struct vgcs_bss *bss; + struct vgcs_bss_cell *cell; + + if (!gsmnet->asci.enable) { + asci_disabled(vty); + return CMD_WARNING; + } + + llist_for_each_entry(trans, &gsmnet->trans_list, entry) { + if (trans->type == TRANS_GCC) + typestr = "Group"; + else if (trans->type == TRANS_BCC) + typestr = "Broadcast"; + else + continue; + vty_out(vty, "Call Reference %s (Voice %s Call).%s", gsm44068_group_id_string(trans->callref), + typestr, VTY_NEWLINE); + vty_out(vty, " Call state : %s%s", vgcs_bcc_gcc_state_name(trans->gcc.fi), VTY_NEWLINE); + vty_out(vty, " Uplink state: %s%s", (trans->gcc.uplink_busy) ? "busy" : "free", VTY_NEWLINE); + if (trans->gcc.uplink_busy) + vty_out(vty, " Talker : %s subscriber%s", + (trans->gcc.uplink_originator) ? "calling" : "other", VTY_NEWLINE); + llist_for_each_entry(bss, &trans->gcc.bss_list, list) { + vty_out(vty, " BSS %8s: listening%s%s", osmo_ss7_pointcode_print(NULL, bss->pc), + (trans->gcc.uplink_busy && bss == trans->gcc.uplink_bss) ? "+talking" : "", + VTY_NEWLINE); + llist_for_each_entry(cell, &bss->cell_list, list_bss) { + vty_out(vty, " Cell %6d: listening%s%s", cell->cell_id, + (trans->gcc.uplink_busy && cell == trans->gcc.uplink_cell) ? "+talking" : "", + VTY_NEWLINE); + } + } + } + + return CMD_SUCCESS; +} + +/*********************************************************************** + * GCR Config Node + ***********************************************************************/ + +static struct cmd_node asci_node = { + ASCI_NODE, + "%s(config-asci)# ", + 1, +}; + +static struct cmd_node gcr_node = { + GCR_NODE, + "%s(config-gcr)# ", + 1, +}; + +char conf_prompt[64]; + +static struct cmd_node vgc_node = { + VGC_NODE, + conf_prompt, + 1, +}; + +static struct cmd_node vbc_node = { + VBC_NODE, + conf_prompt, + 1, +}; + +DEFUN(cfg_asci, cfg_asci_cmd, + "asci", "Enable and configure " ASCI_STR) +{ + vty->node = ASCI_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_enable_disable, cfg_enable_disable_cmd, + "(enable|disable)", "Enable " ASCI_STR "Disable " ASCI_STR) +{ + gsmnet->asci.enable = (argv[0][0] == 'e'); + return CMD_SUCCESS; +} + +DEFUN(cfg_gcr, cfg_gcr_cmd, + "gcr", "Configure Group Call Register") +{ + vty->node = GCR_NODE; + return CMD_SUCCESS; +} + +static bool valid_group_id(const char *id, struct vty *vty) +{ + int i; + + if (strlen(id) < 1 || strlen(id) > 8) { + vty_out(vty, "%%Given group ID is not valid. Use up to 8 numeric digits!%s", VTY_NEWLINE); + return false; + } + for (i = 0; i < strlen(id); i++) { + if (id[i] < '0' || id[i] > '9') { + vty_out(vty, "%%Given group ID is not valid. Use numeric digits only!%s", VTY_NEWLINE); + return false; + } + } + + return true; +} + +DEFUN(cfg_vgc, cfg_vgc_cmd, + "vgc ID", "Configure Voice Group Call\n" "Group ID") +{ + struct gcr *gcr; + + if (!valid_group_id(argv[0], vty)) + return CMD_WARNING; + + gcr = gcr_by_group_id(gsmnet, TRANS_GCC, argv[0]); + if (!gcr) + gcr = gcr_create(gsmnet, TRANS_GCC, argv[0]); + if (!gcr) + return CMD_WARNING; + + sprintf(conf_prompt, "%%s(vgc-%s)# ", gcr->group_id); + vty->node = VGC_NODE; + vty->index = gcr; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_vgc, cfg_no_vgc_cmd, + "no vgc ID", NO_STR "Configure Voice Group Call\n" "Group ID") +{ + struct gcr *gcr; + + if (!valid_group_id(argv[0], vty)) + return CMD_WARNING; + + gcr = gcr_by_group_id(gsmnet, TRANS_GCC, argv[0]); + if (!gcr) { + vty_out(vty, "%%Voice group call with given group ID does not exit!%s", VTY_NEWLINE); + return CMD_WARNING; + } + gcr_destroy(gcr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_vbc, cfg_vbc_cmd, + "vbc ID", "Configure Voice Broadcast Call\n" "Group ID") +{ + struct gcr *gcr; + + if (!valid_group_id(argv[0], vty)) + return CMD_WARNING; + + gcr = gcr_by_group_id(gsmnet, TRANS_BCC, argv[0]); + if (!gcr) + gcr = gcr_create(gsmnet, TRANS_BCC, argv[0]); + if (!gcr) + return CMD_WARNING; + + sprintf(conf_prompt, "%%s(vbc-%s)# ", gcr->group_id); + vty->node = VBC_NODE; + vty->index = gcr; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_vbc, cfg_no_vbc_cmd, + "no vbc ID", NO_STR "Configure Voice Broadcast Call\n" "Group ID") +{ + struct gcr *gcr; + + if (!valid_group_id(argv[0], vty)) + return CMD_WARNING; + + gcr = gcr_by_group_id(gsmnet, TRANS_BCC, argv[0]); + if (!gcr) { + vty_out(vty, "%%Voice broadcast call with given group ID does not exit!%s", VTY_NEWLINE); + return CMD_WARNING; + } + gcr_destroy(gcr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_mute, cfg_mute_cmd, + "mute-talker", "Mute talker's downlink") +{ + struct gcr *gcr = vty->index; + + gcr->mute_talker = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_unmute, cfg_unmute_cmd, + "unmute-talker", "Unmute talker's downlink") +{ + struct gcr *gcr = vty->index; + + gcr->mute_talker = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_timeout, cfg_timeout_cmd, + "timeout <1-65535>", "Set inactivity timer\n" "Timeout in seconds") +{ + struct gcr *gcr = vty->index; + + gcr->timeout = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_timeout, cfg_no_timeout_cmd, + "no timeout", NO_STR "Unset inactivity timer") +{ + struct gcr *gcr = vty->index; + + gcr->timeout = 0; + + return CMD_SUCCESS; +} + +#define PC_ID_STR "Point code of MSC\nCell ID of BTS" + +DEFUN(cfg_no_cell, cfg_no_cell_cmd, + "no cell POINT_CODE [<0-65535>]", NO_STR "Remove BSS/cell from current group\n" PC_ID_STR) +{ + struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(0); + struct gcr *gcr = vty->index; + struct gcr_bss *bss; + int pc = osmo_ss7_pointcode_parse(ss7, argv[0]); + uint16_t cell_id; + + if (pc < 0 || !osmo_ss7_pc_is_valid((uint32_t)pc)) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + bss = gcr_find_bss(gcr, pc); + if (!bss) { + vty_out(vty, "%%Given BSS point code does not exit in list!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (argc > 1) { + cell_id = atoi(argv[1]); + if (!gcr_find_cell(bss, cell_id)) { + vty_out(vty, "%%Given cell does not exit in list!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Remove cell only. Exit if there are still cells for this BSS. */ + gcr_rm_cell(bss, cell_id); + if (!llist_empty(&bss->cell_list)) + return CMD_SUCCESS; + } + + gcr_rm_bss(gcr, pc); + + return CMD_SUCCESS; +} + +DEFUN(cfg_cell, cfg_cell_cmd, + "cell POINT_CODE <0-65535>", "Add cell to current group\n" PC_ID_STR) +{ + struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(0); + struct gcr *gcr = vty->index; + struct gcr_bss *bss; + int pc = osmo_ss7_pointcode_parse(ss7, argv[0]); + uint16_t cell_id = atoi(argv[1]); + + if (pc < 0 || !osmo_ss7_pc_is_valid((uint32_t)pc)) { + vty_out(vty, "Invalid point code (%s)%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + bss = gcr_find_bss(gcr, pc); + if (!bss) + bss = gcr_add_bss(gcr, pc); + if (!bss) + return CMD_WARNING; + + if (gcr_find_cell(bss, cell_id)) + return CMD_SUCCESS; + + gcr_add_cell(bss, cell_id); + + return CMD_SUCCESS; +} + +static int config_write_asci(struct vty *vty) +{ + struct osmo_ss7_instance *ss7 = osmo_ss7_instance_find(0); + struct gcr *gcr; + struct gcr_bss *b; + struct gcr_cell *c; + + vty_out(vty, "asci%s", VTY_NEWLINE); + + vty_out(vty, " %s%s", (gsmnet->asci.enable) ? "enable" : "disable", VTY_NEWLINE); + + vty_out(vty, " gcr%s", VTY_NEWLINE); + + llist_for_each_entry(gcr, &gsmnet->asci.gcr_lists, list) { + vty_out(vty, " %s %s%s", (gcr->trans_type == TRANS_GCC) ? "vgc" : "vbc", gcr->group_id, VTY_NEWLINE); + if (gcr->trans_type == TRANS_GCC) { + if (gcr->timeout) + vty_out(vty, " timeout %d%s", gcr->timeout, VTY_NEWLINE); + else + vty_out(vty, " no timeout%s", VTY_NEWLINE); + } + if (gcr->mute_talker) + vty_out(vty, " mute-talker%s", VTY_NEWLINE); + else + vty_out(vty, " unmute-talker%s", VTY_NEWLINE); + if (llist_empty(&gcr->bss_list)) + vty_out(vty, " ! Please add cell(s) here!%s", VTY_NEWLINE); + llist_for_each_entry(b, &gcr->bss_list, list) { + llist_for_each_entry(c, &b->cell_list, list) + vty_out(vty, " cell %s %d%s", osmo_ss7_pointcode_print(ss7, b->pc), c->cell_id, VTY_NEWLINE); + } + } + + return CMD_SUCCESS; +} + +void asci_vty_init(struct gsm_network *msc_network) +{ + OSMO_ASSERT(gsmnet == NULL); + gsmnet = msc_network; + + install_element_ve(&asci_show_cmd); + /* enable node */ + install_element(ENABLE_NODE, &asci_call_cmd); + /* Config node */ + install_element(CONFIG_NODE, &cfg_asci_cmd); + install_node(&asci_node, config_write_asci); + install_element(ASCI_NODE, &cfg_enable_disable_cmd); + install_element(ASCI_NODE, &cfg_gcr_cmd); + install_node(&gcr_node, NULL); + install_element(GCR_NODE, &cfg_vgc_cmd); + install_element(GCR_NODE, &cfg_no_vgc_cmd); + install_node(&vgc_node, NULL); + install_element(GCR_NODE, &cfg_vbc_cmd); + install_element(GCR_NODE, &cfg_no_vbc_cmd); + install_node(&vbc_node, NULL); + install_element(VGC_NODE, &cfg_mute_cmd); + install_element(VGC_NODE, &cfg_unmute_cmd); + install_element(VGC_NODE, &cfg_timeout_cmd); + install_element(VGC_NODE, &cfg_no_timeout_cmd); + install_element(VGC_NODE, &cfg_cell_cmd); + install_element(VGC_NODE, &cfg_no_cell_cmd); + /* Add all VGC_NODEs again for VBC_NODEs. */ + install_element(VBC_NODE, &cfg_mute_cmd); + install_element(VBC_NODE, &cfg_unmute_cmd); + install_element(VBC_NODE, &cfg_cell_cmd); + install_element(VBC_NODE, &cfg_no_cell_cmd); +} diff --git a/src/libmsc/call_leg.c b/src/libmsc/call_leg.c index 794eda286..59f2f636d 100644 --- a/src/libmsc/call_leg.c +++ b/src/libmsc/call_leg.c @@ -1,25 +1,21 @@ /* Implementation to manage two RTP streams that make up an MO or MT call leg's RTP forwarding. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <osmocom/core/fsm.h> #include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> @@ -43,7 +39,7 @@ enum call_leg_state { }; struct osmo_tdef g_mgw_tdefs[] = { - { .T=-1, .default_val=4, .desc="MGCP response timeout" }, + { .T=-2427, .default_val=4, .desc="MGCP response timeout" }, { .T=-2, .default_val=30, .desc="RTP stream establishing timeout" }, {} }; @@ -126,7 +122,13 @@ void call_leg_release(struct call_leg *cl) static void call_leg_mgw_endpoint_gone(struct call_leg *cl) { + struct mgcp_client *mgcp_client; int i; + + /* Put MGCP client back into MGW pool */ + mgcp_client = osmo_mgcpc_ep_client(cl->mgw_endpoint); + mgcp_client_pool_put(mgcp_client); + cl->mgw_endpoint = NULL; for (i = 0; i < ARRAY_SIZE(cl->rtp); i++) { if (!cl->rtp[i]) @@ -156,7 +158,8 @@ static void call_leg_fsm_establishing_established(struct osmo_fsm_inst *fi, uint } if (!established) break; - call_leg_state_chg(cl, CALL_LEG_ST_ESTABLISHED); + if (cl->fi->state != CALL_LEG_ST_ESTABLISHED) + call_leg_state_chg(cl, CALL_LEG_ST_ESTABLISHED); break; case CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE: @@ -186,6 +189,12 @@ void call_leg_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_st void call_leg_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { + /* Trigger termination of children FSMs (rtp_stream(s)) before + * terminating ourselves, otherwise we are not able to receive + * CALL_LEG_EV_MGW_ENDPOINT_GONE from cl->mgw_endpoint (call_leg => + * rtp_stream => mgw_endpoint), because osmo_fsm disabled dispatching + * events to an FSM in process of terminating. */ + osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL); osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); } @@ -279,16 +288,24 @@ int call_leg_ensure_rtp_alloc(struct call_leg *cl, enum rtp_direction dir, uint3 if (cl->rtp[dir]) return 0; - if (!cl->mgw_endpoint) + if (!cl->mgw_endpoint) { + struct mgcp_client *mgcp_client = mgcp_client_pool_get(gsmnet->mgw.mgw_pool); + if (!mgcp_client) { + LOG_CALL_LEG(cl, LOGL_ERROR, + "cannot ensure MGW endpoint -- no MGW configured, check configuration!\n"); + return -ENODEV; + } cl->mgw_endpoint = osmo_mgcpc_ep_alloc(cl->fi, CALL_LEG_EV_MGW_ENDPOINT_GONE, - gsmnet->mgw.client, gsmnet->mgw.tdefs, cl->fi->id, - "%s", mgcp_client_rtpbridge_wildcard(gsmnet->mgw.client)); + mgcp_client, gsmnet->mgw.tdefs, cl->fi->id, + "%s", mgcp_client_rtpbridge_wildcard(mgcp_client)); + } if (!cl->mgw_endpoint) { LOG_CALL_LEG(cl, LOGL_ERROR, "failed to setup MGW endpoint\n"); return -EIO; } - cl->rtp[dir] = rtp_stream_alloc(cl, dir, call_id, for_trans); + cl->rtp[dir] = rtp_stream_alloc(cl->fi, CALL_LEG_EV_RTP_STREAM_GONE, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE, + CALL_LEG_EV_RTP_STREAM_ESTABLISHED, dir, call_id, for_trans); OSMO_ASSERT(cl->rtp[dir]); return 0; } @@ -301,7 +318,7 @@ struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direct rtps = cl->rtp[dir]; if (!rtps) return NULL; - if (!osmo_sockaddr_str_is_set(&rtps->local)) + if (!osmo_sockaddr_str_is_nonzero(&rtps->local)) return NULL; return &rtps->local; } @@ -309,25 +326,26 @@ struct osmo_sockaddr_str *call_leg_local_ip(struct call_leg *cl, enum rtp_direct /* Make sure an MGW endpoint CI is set up for an RTP connection. * This is the one-stop for all to either completely set up a new endpoint connection, or to modify an existing one. * If not yet present, allocate the rtp_stream for the given direction. - * Then, call rtp_stream_set_codec() if codec_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if + * Then, call rtp_stream_set_codecs() if codecs_if_known is non-NULL, and/or rtp_stream_set_remote_addr() if * remote_addr_if_known is non-NULL. * Finally make sure that a CRCX is sent out for this direction, if this has not already happened. * If the CRCX has already happened but new codec / remote_addr data was passed, call rtp_stream_commit() to trigger an * MDCX. */ int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t call_id, struct gsm_trans *for_trans, - const enum mgcp_codecs *codec_if_known, const struct osmo_sockaddr_str *remote_addr_if_known) + const struct sdp_audio_codecs *codecs_if_known, + const struct osmo_sockaddr_str *remote_addr_if_known) { if (call_leg_ensure_rtp_alloc(cl, dir, call_id, for_trans)) return -EIO; - cl->rtp[dir]->crcx_conn_mode = cl->crcx_conn_mode[dir]; + rtp_stream_set_mode(cl->rtp[dir], cl->crcx_conn_mode[dir]); if (dir == RTP_TO_RAN && cl->ran_peer_supports_osmux) { cl->rtp[dir]->use_osmux = true; cl->rtp[dir]->remote_osmux_cid = -1; /* wildcard */ } - if (codec_if_known) - rtp_stream_set_codec(cl->rtp[dir], *codec_if_known); - if (remote_addr_if_known && osmo_sockaddr_str_is_set(remote_addr_if_known)) + if (codecs_if_known) + rtp_stream_set_codecs(cl->rtp[dir], codecs_if_known); + if (remote_addr_if_known && osmo_sockaddr_str_is_nonzero(remote_addr_if_known)) rtp_stream_set_remote_addr(cl->rtp[dir], remote_addr_if_known); return rtp_stream_ensure_ci(cl->rtp[dir], cl->mgw_endpoint); } @@ -335,22 +353,46 @@ int call_leg_ensure_ci(struct call_leg *cl, enum rtp_direction dir, uint32_t cal int call_leg_local_bridge(struct call_leg *cl1, uint32_t call_id1, struct gsm_trans *trans1, struct call_leg *cl2, uint32_t call_id2, struct gsm_trans *trans2) { - enum mgcp_codecs codec; + struct sdp_audio_codecs *cn_codecs = NULL; cl1->local_bridge = cl2; cl2->local_bridge = cl1; - /* We may just copy the codec info we have for the RAN side of the first leg to the CN side of both legs. This - * also means that if both legs use different codecs the MGW must perform transcoding on the second leg. */ - if (!cl1->rtp[RTP_TO_RAN] || !cl1->rtp[RTP_TO_RAN]->codec_known) { - LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side RTP stream codec is not known, not ready for bridging\n"); + /* Marry the two CN sides of the call legs. Call establishment should have made all efforts for these to be + * compatible. However, for local bridging, the codecs and payload type numbers must be exactly identical on + * both sides. Both sides may so far have different payload type numbers or slightly differing codecs, but it + * will only work when the SDP on the RTP_TO_CN sides of the call legs talk the same payload type numbers. + * So, simply take the SDP from one RTP_TO_CN side, and overwrite the other RTP_TO_CN side's SDP with it. + * If all goes to plan, the codecs will be identical, or possibly the MGW will do a conversion like AMR-BE to + * AMR-OA. In the worst case, the other call leg cannot transcode, and the call fails -- because codec + * negotiation did not do a good enough job. + * + * Copy one call leg's CN config to the other: + * + * call leg 1 call leg 2 + * ---MGW-ep------- ---MGW-ep------- + * RAN CN CN RAN + * AMR:112 AMR:112 AMR:96 AMR:96 + * | + * +-------+ + * | + * V + * AMR:112 AMR:112 AMR:112 AMR:96 + * ^MGW-endpoint converts payload type numbers between 112 and 96. + */ + if (cl1->rtp[RTP_TO_CN] && cl1->rtp[RTP_TO_CN]->codecs_known) + cn_codecs = &cl1->rtp[RTP_TO_CN]->codecs; + else if (cl2->rtp[RTP_TO_CN] && cl2->rtp[RTP_TO_CN]->codecs_known) + cn_codecs = &cl2->rtp[RTP_TO_CN]->codecs; + if (!cn_codecs) { + LOG_CALL_LEG(cl1, LOGL_ERROR, "RAN-side CN stream codec is not known, not ready for bridging\n"); + LOG_CALL_LEG(cl2, LOGL_ERROR, "RAN-side CN stream codec is not known, not ready for bridging\n"); return -EINVAL; } - codec = cl1->rtp[RTP_TO_RAN]->codec; call_leg_ensure_ci(cl1, RTP_TO_CN, call_id1, trans1, - &codec, &cl2->rtp[RTP_TO_CN]->local); + cn_codecs, &cl2->rtp[RTP_TO_CN]->local); call_leg_ensure_ci(cl2, RTP_TO_CN, call_id2, trans2, - &codec, &cl1->rtp[RTP_TO_CN]->local); + cn_codecs, &cl1->rtp[RTP_TO_CN]->local); return 0; } diff --git a/src/libmsc/cell_id_list.c b/src/libmsc/cell_id_list.c index ca7a6d43b..4bf3a76de 100644 --- a/src/libmsc/cell_id_list.c +++ b/src/libmsc/cell_id_list.c @@ -1,25 +1,21 @@ /* Manage a list of struct gsm0808_cell_id */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <osmocom/msc/cell_id_list.h> diff --git a/src/libmsc/codec_filter.c b/src/libmsc/codec_filter.c new file mode 100644 index 000000000..7511f9026 --- /dev/null +++ b/src/libmsc/codec_filter.c @@ -0,0 +1,163 @@ +/* Filter/overlay codec selections for a voice call, across MS, RAN and CN limitations */ +/* + * (C) 2019-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 <osmocom/gsm/protocol/gsm_08_08.h> + +#include <osmocom/msc/codec_filter.h> +#include <osmocom/msc/codec_mapping.h> +#include <osmocom/msc/debug.h> + +/* Add all known payload types encountered in GSM networks */ +static void sdp_add_all_geran_codecs(struct sdp_audio_codecs *ac) +{ + /* In order of preference. TODO: make configurable */ + static const enum gsm48_bcap_speech_ver mobile_codecs[] = { + GSM48_BCAP_SV_AMR_F /*!< 4 GSM FR V3 (FR AMR) */, + GSM48_BCAP_SV_AMR_H /*!< 5 GSM HR V3 (HR_AMR) */, + GSM48_BCAP_SV_EFR /*!< 2 GSM FR V2 (GSM EFR) */, + GSM48_BCAP_SV_FR /*!< 0 GSM FR V1 (GSM FR) */, + GSM48_BCAP_SV_HR /*!< 1 GSM HR V1 (GSM HR) */, + }; + int i; + for (i = 0; i < ARRAY_SIZE(mobile_codecs); i++) + sdp_audio_codecs_add_speech_ver(ac, mobile_codecs[i]); +} + +/* Add all known AMR payload types encountered in UTRAN networks */ +static void sdp_add_all_utran_codecs(struct sdp_audio_codecs *ac) +{ + /* In order of preference. TODO: make configurable */ + static const enum gsm48_bcap_speech_ver utran_codecs[] = { + GSM48_BCAP_SV_AMR_F /*!< 4 GSM FR V3 (FR AMR) */, + GSM48_BCAP_SV_AMR_H /*!< 5 GSM HR V3 (HR_AMR) */, + GSM48_BCAP_SV_AMR_OH /*!< 11 GSM HR V6 (OHR AMR) */, + GSM48_BCAP_SV_AMR_FW /*!< 8 GSM FR V5 (FR AMR-WB) */, + GSM48_BCAP_SV_AMR_OFW /*!< 6 GSM FR V4 (OFR AMR-WB) */, + GSM48_BCAP_SV_AMR_OHW /*!< 7 GSM HR V4 (OHR AMR-WB) */, + }; + int i; + for (i = 0; i < ARRAY_SIZE(utran_codecs); i++) + sdp_audio_codecs_add_speech_ver(ac, utran_codecs[i]); +} + +void codec_filter_set_ran(struct codec_filter *codec_filter, enum osmo_rat_type ran_type) +{ + codec_filter->ran = (struct sdp_audio_codecs){}; + + switch (ran_type) { + default: + case OSMO_RAT_GERAN_A: + sdp_add_all_geran_codecs(&codec_filter->ran); + break; + + case OSMO_RAT_UTRAN_IU: + sdp_add_all_utran_codecs(&codec_filter->ran); + break; + } +} + +void codec_filter_set_bss(struct codec_filter *codec_filter, + const struct gsm0808_speech_codec_list *codec_list_bss_supported) +{ + codec_filter->bss = (struct sdp_audio_codecs){}; + if (codec_list_bss_supported) + sdp_audio_codecs_from_speech_codec_list(&codec_filter->bss, codec_list_bss_supported); +} + +/* Render intersections of all known audio codec constraints to reach a resulting choice of favorite audio codec, plus + * possible set of alternative audio codecs, in codec_filter->result. (The result.rtp address remains unchanged.) */ +int codec_filter_run(struct codec_filter *codec_filter, struct sdp_msg *result, const struct sdp_msg *remote) +{ + struct sdp_audio_codecs *r = &result->audio_codecs; + struct sdp_audio_codec *a = &codec_filter->assignment; + *r = codec_filter->ran; + if (codec_filter->ms.count) + sdp_audio_codecs_intersection(r, &codec_filter->ms, false); + if (codec_filter->bss.count) + sdp_audio_codecs_intersection(r, &codec_filter->bss, false); + if (remote->audio_codecs.count) + sdp_audio_codecs_intersection(r, &remote->audio_codecs, true); + + if (sdp_audio_codec_is_set(a)) { + /* Assignment has completed, the chosen codec should be the first of the resulting SDP. + * If present, make sure this is listed in first place. + * If 'select' is NULL, the assigned codec is not present in the intersection of possible choices for + * TFO. Just omit the assigned codec from the filter result, and it is the CC code's responsibility to + * detect this and assign a working codec instead. */ + struct sdp_audio_codec *select = sdp_audio_codecs_by_descr(r, a); + if (select) + sdp_audio_codecs_select(r, select); + } + return 0; +} + +int codec_filter_to_str_buf(char *buf, size_t buflen, const struct codec_filter *codec_filter, + const struct sdp_msg *result, const struct sdp_msg *remote) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, result); + OSMO_STRBUF_PRINTF(sb, " (from:"); + + if (sdp_audio_codec_is_set(&codec_filter->assignment)) { + OSMO_STRBUF_PRINTF(sb, " assigned="); + OSMO_STRBUF_APPEND(sb, sdp_audio_codec_to_str_buf, &codec_filter->assignment); + } + + if (remote->audio_codecs.count + || osmo_sockaddr_str_is_nonzero(&remote->rtp)) { + OSMO_STRBUF_PRINTF(sb, " remote="); + OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, remote); + } + + if (codec_filter->ms.count) { + OSMO_STRBUF_PRINTF(sb, " MS={"); + OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &codec_filter->ms); + OSMO_STRBUF_PRINTF(sb, "}"); + } + + if (codec_filter->bss.count) { + OSMO_STRBUF_PRINTF(sb, " bss={"); + OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &codec_filter->bss); + OSMO_STRBUF_PRINTF(sb, "}"); + } + + OSMO_STRBUF_PRINTF(sb, " RAN={"); + OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &codec_filter->ran); + OSMO_STRBUF_PRINTF(sb, "}"); + + OSMO_STRBUF_PRINTF(sb, ")"); + + return sb.chars_needed; +} + +char *codec_filter_to_str_c(void *ctx, const struct codec_filter *codec_filter, const struct sdp_msg *result, + const struct sdp_msg *remote) +{ + OSMO_NAME_C_IMPL(ctx, 128, "codec_filter_to_str_c-ERROR", codec_filter_to_str_buf, codec_filter, result, remote) +} + +const char *codec_filter_to_str(const struct codec_filter *codec_filter, const struct sdp_msg *result, + const struct sdp_msg *remote) +{ + return codec_filter_to_str_c(OTC_SELECT, codec_filter, result, remote); +} diff --git a/src/libmsc/codec_mapping.c b/src/libmsc/codec_mapping.c new file mode 100644 index 000000000..bb5968f0e --- /dev/null +++ b/src/libmsc/codec_mapping.c @@ -0,0 +1,547 @@ +/* Routines for translation between codec representations: SDP, CC/BSSMAP variants, MGCP, MNCC */ +/* + * (C) 2019-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 <string.h> + +#include <osmocom/gsm/mncc.h> + +#include <osmocom/msc/sdp_msg.h> +#include <osmocom/msc/codec_mapping.h> +#include <osmocom/msc/mncc.h> + +const struct codec_mapping codec_map[] = { + /* FIXME: sdp.fmtp handling is not done properly yet, proper mode-set and octet-align handling will follow in + * separate patches. */ + { + .sdp = { + .payload_type = 0, + .subtype_name = "PCMU", + .rate = 8000, + }, + .mgcp = CODEC_PCMU_8000_1, + }, + { + .sdp = { + .payload_type = 3, + .subtype_name = "GSM", + .rate = 8000, + }, + .mgcp = CODEC_GSM_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_FR }, + .mncc_payload_msg_type = GSM_TCHF_FRAME, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_FR1, + }, + .perm_speech = GSM0808_PERM_FR1, + .frhr = CODEC_FRHR_FR, + }, + { + .sdp = { + .payload_type = 8, + .subtype_name = "PCMA", + .rate = 8000, + }, + .mgcp = CODEC_PCMA_8000_1, + }, + { + .sdp = { + .payload_type = 18, + .subtype_name = "G729", + .rate = 8000, + }, + .mgcp = CODEC_G729_8000_1, + }, + { + .sdp = { + .payload_type = 110, + .subtype_name = "GSM-EFR", + .rate = 8000, + }, + .mgcp = CODEC_GSMEFR_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_EFR }, + .mncc_payload_msg_type = GSM_TCHF_FRAME_EFR, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_FR2, + }, + .perm_speech = GSM0808_PERM_FR2, + .frhr = CODEC_FRHR_FR, + }, + { + .sdp = { + .payload_type = 111, + .subtype_name = "GSM-HR-08", + .rate = 8000, + }, + .mgcp = CODEC_GSMHR_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_HR }, + .mncc_payload_msg_type = GSM_TCHH_FRAME, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_HR1, + }, + .perm_speech = GSM0808_PERM_HR1, + .frhr = CODEC_FRHR_HR, + }, + { + .sdp = { + /* 112 is just what we use by default. The other call leg may impose a different number. */ + .payload_type = 112, + .subtype_name = "AMR", + .rate = 8000, + /* AMR is always octet-aligned in 2G and 3G RAN, so this fmtp is signalled to remote call legs. + * So far, fmtp is ignored in incoming SIP SDP, so an incoming SDP without 'octet-align=1' will + * match with this entry; we will still reply with 'octet-align=1', which often works out. */ + .fmtp = "octet-align=1", + }, + .mgcp = CODEC_AMR_8000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_AMR_F }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_FR3, + .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR, + }, + .perm_speech = GSM0808_PERM_FR3, + .frhr = CODEC_FRHR_FR, + }, + { + /* Another entry like the above, to map HR3 to AMR, too. */ + .sdp = { + .payload_type = 112, + .subtype_name = "AMR", + .rate = 8000, + .fmtp = "octet-align=1", + }, + .mgcp = CODEC_AMR_8000_1, + .speech_ver_count = 2, + .speech_ver = { GSM48_BCAP_SV_AMR_H, GSM48_BCAP_SV_AMR_OH }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_HR3, + .cfg = GSM0808_SC_CFG_DEFAULT_HR_AMR, + }, + .perm_speech = GSM0808_PERM_HR3, + .frhr = CODEC_FRHR_HR, + }, + { + .sdp = { + .payload_type = 113, + .subtype_name = "AMR-WB", + .rate = 16000, + .fmtp = "octet-align=1", + }, + .mgcp = CODEC_AMRWB_16000_1, + .speech_ver_count = 2, + .speech_ver = { GSM48_BCAP_SV_AMR_OFW, GSM48_BCAP_SV_AMR_FW }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_FR5, + .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR_WB, + }, + .perm_speech = GSM0808_PERM_FR5, + .frhr = CODEC_FRHR_FR, + }, + { + /* Another entry like the above, to map HR4 to AMR-WB, too. */ + .sdp = { + .payload_type = 113, + .subtype_name = "AMR-WB", + .rate = 16000, + .fmtp = "octet-align=1", + }, + .mgcp = CODEC_AMRWB_16000_1, + .speech_ver_count = 1, + .speech_ver = { GSM48_BCAP_SV_AMR_OHW }, + .mncc_payload_msg_type = GSM_TCH_FRAME_AMR, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .fi = true, + .type = GSM0808_SCT_HR4, + .cfg = GSM0808_SC_CFG_DEFAULT_OHR_AMR_WB, + }, + .perm_speech = GSM0808_PERM_HR4, + .frhr = CODEC_FRHR_HR, + }, + { + .sdp = { + .payload_type = 96, + .subtype_name = "VND.3GPP.IUFP", + .rate = 16000, + }, + .mgcp = CODEC_IUFP, + }, + { + .sdp = { + .payload_type = 120, + .subtype_name = "CLEARMODE", + .rate = 8000, + }, + .has_gsm0808_speech_codec = true, + .gsm0808_speech_codec = { + .pi = true, /* PI indicates CSDoIP is supported */ + .pt = false, /* PT indicates CSDoTDM is not supported */ + .type = GSM0808_SCT_CSD, + .cfg = 0, /* R2/R3 not set (redundancy not supported) */ + }, + .mgcp = CODEC_CLEARMODE, + }, +}; + +#define foreach_codec_mapping(CODEC_MAPPING) \ + for ((CODEC_MAPPING) = codec_map; (CODEC_MAPPING) < codec_map + ARRAY_SIZE(codec_map); (CODEC_MAPPING)++) + +const struct gsm_mncc_bearer_cap bearer_cap_empty = { + .speech_ver = { -1 }, + }; + +const struct codec_mapping *codec_mapping_by_speech_ver(enum gsm48_bcap_speech_ver speech_ver) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + int i; + for (i = 0; i < m->speech_ver_count; i++) + if (m->speech_ver[i] == speech_ver) + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec_type(enum gsm0808_speech_codec_type sct) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!m->has_gsm0808_speech_codec) + continue; + if (m->gsm0808_speech_codec.type == sct) + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_gsm0808_speech_codec(const struct gsm0808_speech_codec *sc) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!m->has_gsm0808_speech_codec) + continue; + if (m->gsm0808_speech_codec.type != sc->type) + continue; + /* Return only those where sc->cfg is a subset of m->gsm0808_speech_codec.cfg. */ + if ((m->gsm0808_speech_codec.cfg & sc->cfg) != sc->cfg) + continue; + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_perm_speech(enum gsm0808_permitted_speech perm_speech) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (m->perm_speech == perm_speech) + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_subtype_name(const char *subtype_name) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!strcmp(m->sdp.subtype_name, subtype_name)) + return m; + } + return NULL; +} + +const struct codec_mapping *codec_mapping_by_mgcp_codec(enum mgcp_codecs mgcp) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (m->mgcp == mgcp) + return m; + } + return NULL; +} + +/* Append given Speech Version to the end of the Bearer Capabilities Speech Version array. Return 1 if added, zero + * otherwise (as in, return the number of items added). */ +int bearer_cap_add_speech_ver(struct gsm_mncc_bearer_cap *bearer_cap, enum gsm48_bcap_speech_ver speech_ver) +{ + int i; + for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) { + if (bearer_cap->speech_ver[i] == speech_ver) + return 0; + if (bearer_cap->speech_ver[i] == -1) { + bearer_cap->speech_ver[i] = speech_ver; + bearer_cap->speech_ver[i+1] = -1; + return 1; + } + } + return 0; +} + +/* From the current speech_ver list present in the bearer_cap, set the bearer_cap.radio. + * If a HR speech_ver is present, set to GSM48_BCAP_RRQ_DUAL_FR, otherwise set to GSM48_BCAP_RRQ_FR_ONLY. */ +int bearer_cap_set_radio(struct gsm_mncc_bearer_cap *bearer_cap) +{ + bool hr_present = false; + int i; + for (i = 0; i < ARRAY_SIZE(bearer_cap->speech_ver) - 1; i++) { + const struct codec_mapping *m; + + if (bearer_cap->speech_ver[i] == -1) + break; + + m = codec_mapping_by_speech_ver(bearer_cap->speech_ver[i]); + + if (!m) + continue; + + if (m->frhr == CODEC_FRHR_HR) + hr_present = true; + } + + if (hr_present) + bearer_cap->radio = GSM48_BCAP_RRQ_DUAL_FR; + else + bearer_cap->radio = GSM48_BCAP_RRQ_FR_ONLY; + + return 0; +} + +/* Try to convert the SDP audio codec name to Speech Versions to append to Bearer Capabilities. + * Return the number of Speech Version entries added (some may add more than one, others may be unknown/unapplicable and + * return 0). */ +int sdp_audio_codec_add_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codec *codec) +{ + const struct codec_mapping *m; + int added = 0; + foreach_codec_mapping(m) { + int i; + if (strcmp(m->sdp.subtype_name, codec->subtype_name)) + continue; + /* TODO also match rate and fmtp? */ + for (i = 0; i < m->speech_ver_count; i++) + added += bearer_cap_add_speech_ver(bearer_cap, m->speech_ver[i]); + } + return added; +} + +/* Append all audio codecs found in given sdp_msg to Bearer Capability, by traversing all codec entries with + * sdp_audio_codec_add_to_bearer_cap(). Return the number of Speech Version entries added. + * Note that Speech Version entries are only appended, no previous entries are removed. + * Note that only the Speech Version entries are modified; to make a valid Bearer Capabiliy, at least bearer_cap->radio + * must also be set (before or after this function); see also bearer_cap_set_radio(). */ +int sdp_audio_codecs_to_bearer_cap(struct gsm_mncc_bearer_cap *bearer_cap, const struct sdp_audio_codecs *ac) +{ + const struct sdp_audio_codec *codec; + int added = 0; + + sdp_audio_codecs_foreach(codec, ac) { + added += sdp_audio_codec_add_to_bearer_cap(bearer_cap, codec); + } + + return added; +} + +/* Convert Speech Version to SDP audio codec and append to SDP message struct. */ +struct sdp_audio_codec *sdp_audio_codecs_add_speech_ver(struct sdp_audio_codecs *ac, + enum gsm48_bcap_speech_ver speech_ver) +{ + const struct codec_mapping *m; + struct sdp_audio_codec *ret = NULL; + foreach_codec_mapping(m) { + int i; + for (i = 0; i < m->speech_ver_count; i++) { + if (m->speech_ver[i] == speech_ver) { + ret = sdp_audio_codecs_add_copy(ac, &m->sdp); + break; + } + } + } + return ret; +} + +struct sdp_audio_codec *sdp_audio_codecs_add_mgcp_codec(struct sdp_audio_codecs *ac, enum mgcp_codecs mgcp_codec) +{ + const struct codec_mapping *m = codec_mapping_by_mgcp_codec(mgcp_codec); + if (!m) + return NULL; + return sdp_audio_codecs_add_copy(ac, &m->sdp); +} + +void sdp_audio_codecs_from_bearer_cap(struct sdp_audio_codecs *ac, const struct gsm_mncc_bearer_cap *bc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bc->speech_ver); i++) { + if (bc->speech_ver[i] == -1) + break; + sdp_audio_codecs_add_speech_ver(ac, bc->speech_ver[i]); + } +} + +/* Append an entry for the given sdp_audio_codec to the gsm0808_speech_codec_list. + * Return 0 if an entry was added, -ENOENT when there is no mapping to gsm0808_speech_codec for the given + * sdp_audio_codec, and -ENOSPC when scl is full and nothing could be added. */ +int sdp_audio_codec_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codec *codec) +{ + const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name); + if (!m) + return -ENOENT; + if (!m->has_gsm0808_speech_codec) + return -ENOENT; + if (scl->len >= ARRAY_SIZE(scl->codec)) + return -ENOSPC; + scl->codec[scl->len] = m->gsm0808_speech_codec; + /* FIXME: apply AMR configuration according to codec->fmtp */ + scl->len++; + return 0; +} + +void sdp_audio_codecs_to_speech_codec_list(struct gsm0808_speech_codec_list *scl, const struct sdp_audio_codecs *ac) +{ + const struct sdp_audio_codec *codec; + + *scl = (struct gsm0808_speech_codec_list){}; + + sdp_audio_codecs_foreach(codec, ac) { + int rc = sdp_audio_codec_to_speech_codec_list(scl, codec); + if (rc == -ENOSPC) + break; + } +} + +void sdp_audio_codecs_from_speech_codec_list(struct sdp_audio_codecs *ac, const struct gsm0808_speech_codec_list *cl) +{ + int i; + for (i = 0; i < cl->len; i++) { + const struct gsm0808_speech_codec *sc = &cl->codec[i]; + const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec(sc); + if (!m) + continue; + sdp_audio_codecs_add_copy(ac, &m->sdp); + /* FIXME: for AMR, apply sc->cfg to the added codec's fmtp */ + } +} + +int sdp_audio_codecs_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct sdp_audio_codecs *ac) +{ + const struct sdp_audio_codec *codec; + bool fr_present = false; + int first_fr_idx = -1; + bool hr_present = false; + int first_hr_idx = -1; + int idx = -1; + + *ct = (struct gsm0808_channel_type){ + .ch_indctr = GSM0808_CHAN_SPEECH, + }; + + sdp_audio_codecs_foreach(codec, ac) { + const struct codec_mapping *m; + int i; + bool dup; + idx++; + foreach_codec_mapping(m) { + if (strcmp(m->sdp.subtype_name, codec->subtype_name)) + continue; + + switch (m->perm_speech) { + default: + continue; + + case GSM0808_PERM_FR1: + case GSM0808_PERM_FR2: + case GSM0808_PERM_FR3: + case GSM0808_PERM_FR4: + case GSM0808_PERM_FR5: + fr_present = true; + if (first_fr_idx < 0) + first_fr_idx = idx; + break; + + case GSM0808_PERM_HR1: + case GSM0808_PERM_HR2: + case GSM0808_PERM_HR3: + case GSM0808_PERM_HR4: + case GSM0808_PERM_HR6: + hr_present = true; + if (first_hr_idx < 0) + first_hr_idx = idx; + break; + } + + /* Avoid duplicates */ + dup = false; + for (i = 0; i < ct->perm_spch_len; i++) { + if (ct->perm_spch[i] == m->perm_speech) { + dup = true; + break; + } + } + if (dup) + continue; + + ct->perm_spch[ct->perm_spch_len] = m->perm_speech; + ct->perm_spch_len++; + } + } + + if (fr_present && hr_present) { + if (first_fr_idx <= first_hr_idx) + ct->ch_rate_type = GSM0808_SPEECH_FULL_PREF; + else + ct->ch_rate_type = GSM0808_SPEECH_HALF_PREF; + } else if (fr_present && !hr_present) + ct->ch_rate_type = GSM0808_SPEECH_FULL_BM; + else if (!fr_present && hr_present) + ct->ch_rate_type = GSM0808_SPEECH_HALF_LM; + else + return -EINVAL; + return 0; +} + +enum mgcp_codecs sdp_audio_codec_to_mgcp_codec(const struct sdp_audio_codec *codec) +{ + const struct codec_mapping *m; + foreach_codec_mapping(m) { + if (!sdp_audio_codec_cmp(&m->sdp, codec, false, false)) + return m->mgcp; + } + return NO_MGCP_CODEC; +} diff --git a/src/libmsc/csd_bs.c b/src/libmsc/csd_bs.c new file mode 100644 index 000000000..ab0b64309 --- /dev/null +++ b/src/libmsc/csd_bs.c @@ -0,0 +1,517 @@ +/* 3GPP TS 122.002 Bearer Services */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Oliver Smith + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 <errno.h> + +#include <osmocom/msc/csd_bs.h> +#include <osmocom/msc/debug.h> + +/* csd_bs related below */ + +struct csd_bs_map { + /* BS number (20, 21, ...) */ + unsigned int num; + /* Access Structure (1: asynchronous, 0: synchronous) */ + bool async; + /* QoS Attribute (1: transparent, 0: non-transparent) */ + bool transp; + /* Rate Adaption (V110, V120 etc.) */ + enum gsm48_bcap_ra ra; + /* Fixed Network User Rate */ + unsigned int rate; +}; + +static const struct csd_bs_map bs_map[] = { + /* 3.1.1.1.2 */ + [CSD_BS_21_T_V110_0k3] = { + .num = 21, + .async = true, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 300, + }, + [CSD_BS_22_T_V110_1k2] = { + .num = 22, + .async = true, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 1200, + }, + [CSD_BS_24_T_V110_2k4] = { + .num = 24, + .async = true, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 2400, + }, + [CSD_BS_25_T_V110_4k8] = { + .num = 25, + .async = true, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 4800, + }, + [CSD_BS_26_T_V110_9k6] = { + .num = 26, + .async = true, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 9600, + }, + + /* 3.1.1.2.2 */ + [CSD_BS_21_NT_V110_0k3] = { + .num = 21, + .async = true, + .transp = false, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 300, + }, + [CSD_BS_22_NT_V110_1k2] = { + .num = 22, + .async = true, + .transp = false, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 1200, + }, + [CSD_BS_24_NT_V110_2k4] = { + .num = 24, + .async = true, + .transp = false, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 2400, + }, + [CSD_BS_25_NT_V110_4k8] = { + .num = 25, + .async = true, + .transp = false, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 4800, + }, + [CSD_BS_26_NT_V110_9k6] = { + .num = 26, + .async = true, + .transp = false, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 9600, + }, + + /* 3.1.2.1.2 */ + [CSD_BS_31_T_V110_1k2] = { + .num = 31, + .async = false, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 1200, + }, + [CSD_BS_32_T_V110_2k4] = { + .num = 32, + .async = false, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 2400, + }, + [CSD_BS_33_T_V110_4k8] = { + .num = 33, + .async = false, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 4800, + }, + [CSD_BS_34_T_V110_9k6] = { + .num = 34, + .async = false, + .transp = true, + .ra = GSM48_BCAP_RA_V110_X30, + .rate = 9600, + }, +}; + +osmo_static_assert(ARRAY_SIZE(bs_map) == CSD_BS_MAX, _invalid_size_bs_map); + +bool csd_bs_is_transp(enum csd_bs bs) +{ + return bs_map[bs].transp; +} + +/* Short single-line representation, convenient for logging. + * Like "BS25NT" */ +int csd_bs_to_str_buf(char *buf, size_t buflen, enum csd_bs bs) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + const struct csd_bs_map *map = &bs_map[bs]; + + OSMO_STRBUF_PRINTF(sb, "BS%u%s", + map->num, + map->transp ? "T" : "NT"); + + if (map->ra != GSM48_BCAP_RA_V110_X30) + OSMO_STRBUF_PRINTF(sb, "-RA=%d", map->ra); + + return sb.chars_needed; +} + +char *csd_bs_to_str_c(void *ctx, enum csd_bs bs) +{ + OSMO_NAME_C_IMPL(ctx, 32, "csd_bs_to_str_c-ERROR", csd_bs_to_str_buf, bs) +} + +const char *csd_bs_to_str(enum csd_bs bs) +{ + return csd_bs_to_str_c(OTC_SELECT, bs); +} + +static int csd_bs_to_gsm0808_data_rate_transp(enum csd_bs bs, uint8_t *ch_rate_type) +{ + switch (bs_map[bs].rate) { + case 300: + *ch_rate_type = GSM0808_DATA_FULL_PREF; + return GSM0808_DATA_RATE_TRANSP_600; + case 1200: + *ch_rate_type = GSM0808_DATA_FULL_PREF; + return GSM0808_DATA_RATE_TRANSP_1k2; + case 2400: + *ch_rate_type = GSM0808_DATA_FULL_PREF; + return GSM0808_DATA_RATE_TRANSP_2k4; + case 4800: + *ch_rate_type = GSM0808_DATA_FULL_PREF; + return GSM0808_DATA_RATE_TRANSP_4k8; + case 9600: + *ch_rate_type = GSM0808_DATA_FULL_BM; + return GSM0808_DATA_RATE_TRANSP_9k6; + } + return -EINVAL; +} + +static int csd_bs_to_gsm0808_data_rate_non_transp(enum csd_bs bs, uint8_t *ch_rate_type) +{ + uint16_t rate = bs_map[bs].rate; + + if (rate < 6000) { + *ch_rate_type = GSM0808_DATA_FULL_PREF; + return GSM0808_DATA_RATE_NON_TRANSP_6k0; + } + if (rate < 12000) { + *ch_rate_type = GSM0808_DATA_FULL_BM; + return GSM0808_DATA_RATE_NON_TRANSP_12k0; + } + + return -EINVAL; +} + +static int csd_bs_to_gsm0808_data_rate_non_transp_allowed(enum csd_bs bs) +{ + uint16_t rate = bs_map[bs].rate; + + if (rate < 6000) + return GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_6k0; + if (rate < 12000) + return GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_12k0; + + return -EINVAL; +} + +enum csd_bs csd_bs_from_bearer_cap(const struct gsm_mncc_bearer_cap *cap, bool transp) +{ + enum gsm48_bcap_ra ra = cap->data.rate_adaption; + enum gsm48_bcap_user_rate rate = cap->data.user_rate; + bool async = cap->data.async; + + /* 3.1kHz CSD calls won't have the rate adaptation field set + but do require rate adaptation. */ + if (cap->data.interm_rate && !ra) + ra = GSM48_BCAP_RA_V110_X30; + + if (ra == GSM48_BCAP_RA_V110_X30 && async && transp) { + switch (rate) { + case GSM48_BCAP_UR_300: + return CSD_BS_21_T_V110_0k3; + case GSM48_BCAP_UR_1200: + return CSD_BS_22_T_V110_1k2; + case GSM48_BCAP_UR_2400: + return CSD_BS_24_T_V110_2k4; + case GSM48_BCAP_UR_4800: + return CSD_BS_25_T_V110_4k8; + case GSM48_BCAP_UR_9600: + return CSD_BS_26_T_V110_9k6; + default: + return CSD_BS_NONE; + } + } + + if (ra == GSM48_BCAP_RA_V110_X30 && async && !transp) { + switch (rate) { + case GSM48_BCAP_UR_300: + return CSD_BS_21_NT_V110_0k3; + case GSM48_BCAP_UR_1200: + return CSD_BS_22_NT_V110_1k2; + case GSM48_BCAP_UR_2400: + return CSD_BS_24_NT_V110_2k4; + case GSM48_BCAP_UR_4800: + return CSD_BS_25_NT_V110_4k8; + case GSM48_BCAP_UR_9600: + return CSD_BS_26_NT_V110_9k6; + default: + return CSD_BS_NONE; + } + } + + if (ra == GSM48_BCAP_RA_V110_X30 && !async && transp) { + switch (rate) { + case GSM48_BCAP_UR_1200: + return CSD_BS_31_T_V110_1k2; + case GSM48_BCAP_UR_2400: + return CSD_BS_32_T_V110_2k4; + case GSM48_BCAP_UR_4800: + return CSD_BS_33_T_V110_4k8; + case GSM48_BCAP_UR_9600: + return CSD_BS_34_T_V110_9k6; + default: + return CSD_BS_NONE; + } + } + + return CSD_BS_NONE; +} + +/* csd_bs_list related below */ + +int csd_bs_list_to_str_buf(char *buf, size_t buflen, const struct csd_bs_list *list) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + int i; + + if (!list->count) + OSMO_STRBUF_PRINTF(sb, "(no-bearer-services)"); + + for (i = 0; i < list->count; i++) { + if (i) + OSMO_STRBUF_PRINTF(sb, ","); + + OSMO_STRBUF_APPEND(sb, csd_bs_to_str_buf, list->bs[i]); + } + return sb.chars_needed; +} + +char *csd_bs_list_to_str_c(void *ctx, const struct csd_bs_list *list) +{ + OSMO_NAME_C_IMPL(ctx, 128, "csd_bs_list_to_str_c-ERROR", csd_bs_list_to_str_buf, list) +} + +const char *csd_bs_list_to_str(const struct csd_bs_list *list) +{ + return csd_bs_list_to_str_c(OTC_SELECT, list); +} + +bool csd_bs_list_has_bs(const struct csd_bs_list *list, enum csd_bs bs) +{ + int i; + + for (i = 0; i < list->count; i++) { + if (list->bs[i] == bs) + return true; + } + + return false; +} + +void csd_bs_list_add_bs(struct csd_bs_list *list, enum csd_bs bs) +{ + int i; + + if (!bs) + return; + + for (i = 0; i < list->count; i++) { + if (list->bs[i] == bs) + return; + } + + list->bs[i] = bs; + list->count++; +} + +void csd_bs_list_remove(struct csd_bs_list *list, enum csd_bs bs) +{ + int i; + bool found = false; + + for (i = 0; i < list->count; i++) { + if (list->bs[i] == bs) + found = true; + if (found && i + 1 < list->count) + list->bs[i] = list->bs[i + 1]; + } + + if (found) + list->count--; +} + +void csd_bs_list_intersection(struct csd_bs_list *dest, const struct csd_bs_list *other) +{ + int i; + + for (i = 0; i < dest->count; i++) { + if (csd_bs_list_has_bs(other, dest->bs[i])) + continue; + csd_bs_list_remove(dest, dest->bs[i]); + i--; + } +} + +int csd_bs_list_to_gsm0808_channel_type(struct gsm0808_channel_type *ct, const struct csd_bs_list *list) +{ + int i; + int rc; + + *ct = (struct gsm0808_channel_type){ + .ch_indctr = GSM0808_CHAN_DATA, + }; + + if (!list->count) + return -EINVAL; + + if (csd_bs_is_transp(list->bs[0])) { + ct->data_transparent = true; + rc = csd_bs_to_gsm0808_data_rate_transp(list->bs[0], &ct->ch_rate_type); + } else { + rc = csd_bs_to_gsm0808_data_rate_non_transp(list->bs[0], &ct->ch_rate_type); + } + + if (rc < 0) + return -EINVAL; + + ct->data_rate = rc; + + /* Other possible data rates allowed (3GPP TS 48.008 § 3.2.2.11, 5a) */ + if (!ct->data_transparent && list->count > 1) { + for (i = 1; i < list->count; i++) { + if (!csd_bs_is_transp(list->bs[i])) + continue; + + rc = csd_bs_to_gsm0808_data_rate_non_transp_allowed(list->bs[i]); + if (rc < 0) { + LOGP(DMSC, LOGL_DEBUG, "Failed to convert %s to allowed r i/f rate\n", + csd_bs_to_str(list->bs[i])); + continue; + } + + ct->data_rate_allowed |= rc; + } + if (ct->data_rate_allowed) + ct->data_rate_allowed_is_set = true; + } + + return 0; +} + +int csd_bs_list_to_bearer_cap(struct gsm_mncc_bearer_cap *cap, const struct csd_bs_list *list) +{ + *cap = (struct gsm_mncc_bearer_cap){ + .transfer = GSM_MNCC_BCAP_UNR_DIG, + .mode = GSM48_BCAP_TMOD_CIRCUIT, + .coding = GSM48_BCAP_CODING_GSM_STD, + .radio = GSM48_BCAP_RRQ_FR_ONLY, + }; + enum csd_bs bs; + int i; + + for (i = 0; i < list->count; i++) { + bs = list->bs[i]; + + cap->data.rate_adaption = GSM48_BCAP_RA_V110_X30; + cap->data.sig_access = GSM48_BCAP_SA_I440_I450; + cap->data.async = bs_map[bs].async; + if (bs_map[bs].transp) + cap->data.transp = GSM48_BCAP_TR_TRANSP; + else + cap->data.transp = GSM48_BCAP_TR_RLP; + + /* FIXME: proper values for sync/async (current: 8N1) */ + cap->data.nr_data_bits = 8; + cap->data.parity = GSM48_BCAP_PAR_NONE; + cap->data.nr_stop_bits = 1; + cap->data.modem_type = GSM48_BCAP_MT_NONE; + + switch (bs_map[bs].rate) { + case 300: + cap->data.user_rate = GSM48_BCAP_UR_300; + cap->data.interm_rate = GSM48_BCAP_IR_8k; + break; + case 1200: + cap->data.user_rate = GSM48_BCAP_UR_1200; + cap->data.interm_rate = GSM48_BCAP_IR_8k; + break; + case 2400: + cap->data.user_rate = GSM48_BCAP_UR_2400; + cap->data.interm_rate = GSM48_BCAP_IR_8k; + break; + case 4800: + cap->data.user_rate = GSM48_BCAP_UR_4800; + cap->data.interm_rate = GSM48_BCAP_IR_8k; + break; + case 9600: + cap->data.user_rate = GSM48_BCAP_UR_9600; + cap->data.interm_rate = GSM48_BCAP_IR_16k; + break; + default: + LOGP(DMSC, LOGL_ERROR, + "%s(): bs=%d (rate=%u) is not implemented\n", + __func__, bs, bs_map[bs].rate); + continue; + } + + /* FIXME: handle more than one list entry */ + return 1; + } + + return 0; +} + +void csd_bs_list_from_bearer_cap(struct csd_bs_list *list, const struct gsm_mncc_bearer_cap *cap) +{ + *list = (struct csd_bs_list){}; + + switch (cap->data.transp) { + case GSM48_BCAP_TR_TRANSP: + csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, true)); + break; + case GSM48_BCAP_TR_RLP: /* NT */ + csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, false)); + break; + case GSM48_BCAP_TR_TR_PREF: + csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, true)); + csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, false)); + break; + case GSM48_BCAP_TR_RLP_PREF: + csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, false)); + csd_bs_list_add_bs(list, csd_bs_from_bearer_cap(cap, true)); + break; + } + + if (!list->count) { + LOGP(DMSC, LOGL_ERROR, "Failed to get bearer service from bearer capabilities ra=%d, async=%d," + " transp=%d, user_rate=%d\n", cap->data.rate_adaption, cap->data.async, cap->data.transp, + cap->data.user_rate); + return; + } +} diff --git a/src/libmsc/csd_filter.c b/src/libmsc/csd_filter.c new file mode 100644 index 000000000..0f428cffe --- /dev/null +++ b/src/libmsc/csd_filter.c @@ -0,0 +1,157 @@ +/* Filter/overlay bearer service selections across MS, RAN and CN limitations */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Oliver Smith + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 <osmocom/mgcp_client/mgcp_client.h> + +#include <osmocom/msc/csd_filter.h> + +static void add_all_geran_bs(struct csd_bs_list *list) +{ + /* See 3GPP TS 122.002 Bearer Services */ + /* In order of preference. TODO: make configurable */ + + /* GSM-R */ + csd_bs_list_add_bs(list, CSD_BS_24_T_V110_2k4); + csd_bs_list_add_bs(list, CSD_BS_25_T_V110_4k8); + csd_bs_list_add_bs(list, CSD_BS_26_T_V110_9k6); + + /* Other */ + csd_bs_list_add_bs(list, CSD_BS_21_T_V110_0k3); + csd_bs_list_add_bs(list, CSD_BS_22_T_V110_1k2); + csd_bs_list_add_bs(list, CSD_BS_21_NT_V110_0k3); + csd_bs_list_add_bs(list, CSD_BS_22_NT_V110_1k2); + csd_bs_list_add_bs(list, CSD_BS_24_NT_V110_2k4); + csd_bs_list_add_bs(list, CSD_BS_25_NT_V110_4k8); + csd_bs_list_add_bs(list, CSD_BS_26_NT_V110_9k6); + csd_bs_list_add_bs(list, CSD_BS_31_T_V110_1k2); + csd_bs_list_add_bs(list, CSD_BS_32_T_V110_2k4); + csd_bs_list_add_bs(list, CSD_BS_33_T_V110_4k8); + csd_bs_list_add_bs(list, CSD_BS_34_T_V110_9k6); +} + +static void add_all_utran_bs(struct csd_bs_list *list) +{ + /* See 3GPP TS 122.002 Bearer Services */ + /* In order of preference. TODO: make configurable */ + csd_bs_list_add_bs(list, CSD_BS_21_NT_V110_0k3); + csd_bs_list_add_bs(list, CSD_BS_22_NT_V110_1k2); + csd_bs_list_add_bs(list, CSD_BS_24_NT_V110_2k4); + csd_bs_list_add_bs(list, CSD_BS_25_NT_V110_4k8); + csd_bs_list_add_bs(list, CSD_BS_26_NT_V110_9k6); +} + +void csd_filter_set_ran(struct csd_filter *filter, enum osmo_rat_type ran_type) +{ + filter->ran = (struct csd_bs_list){}; + + switch (ran_type) { + default: + case OSMO_RAT_GERAN_A: + add_all_geran_bs(&filter->ran); + break; + case OSMO_RAT_UTRAN_IU: + add_all_utran_bs(&filter->ran); + break; + } +} + +int csd_filter_run(struct csd_filter *filter, struct sdp_msg *result, const struct sdp_msg *remote) +{ + struct csd_bs_list *r = &result->bearer_services; + enum csd_bs a = filter->assignment; + + *r = filter->ran; + + if (filter->ms.count) + csd_bs_list_intersection(r, &filter->ms); + if (filter->bss.count) + csd_bs_list_intersection(r, &filter->bss); + if (remote->bearer_services.count) + csd_bs_list_intersection(r, &remote->bearer_services); + + /* Future: If osmo-msc were able to trigger a re-assignment [...] see + * comment in codec_filter_run(). */ + + if (a) { + *r = (struct csd_bs_list){}; + csd_bs_list_add_bs(r, a); + } + + result->audio_codecs.count = 1; + result->audio_codecs.codec[0] = (struct sdp_audio_codec){ + .payload_type = CODEC_CLEARMODE, + .subtype_name = "CLEARMODE", + .rate = 8000, + }; + + return 0; +} + + +int csd_filter_to_str_buf(char *buf, size_t buflen, const struct csd_filter *filter, + const struct sdp_msg *result, const struct sdp_msg *remote) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, result); + OSMO_STRBUF_PRINTF(sb, " (from:"); + + if (filter->assignment) { + OSMO_STRBUF_PRINTF(sb, " assigned="); + OSMO_STRBUF_APPEND(sb, csd_bs_to_str_buf, filter->assignment); + } + + if (remote->bearer_services.count || osmo_sockaddr_str_is_nonzero(&remote->rtp)) { + OSMO_STRBUF_PRINTF(sb, " remote="); + OSMO_STRBUF_APPEND(sb, sdp_msg_to_str_buf, remote); + } + + if (filter->ms.count) { + OSMO_STRBUF_PRINTF(sb, " MS={"); + OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &filter->ms); + OSMO_STRBUF_PRINTF(sb, "}"); + } + + if (filter->bss.count) { + OSMO_STRBUF_PRINTF(sb, " bss={"); + OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &filter->bss); + OSMO_STRBUF_PRINTF(sb, "}"); + } + + OSMO_STRBUF_PRINTF(sb, " RAN={"); + OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &filter->ran); + OSMO_STRBUF_PRINTF(sb, "}"); + + OSMO_STRBUF_PRINTF(sb, ")"); + + return sb.chars_needed; +} + +char *csd_filter_to_str_c(void *ctx, const struct csd_filter *filter, const struct sdp_msg *result, const struct sdp_msg *remote) +{ + OSMO_NAME_C_IMPL(ctx, 128, "csd_filter_to_str_c-ERROR", csd_filter_to_str_buf, filter, result, remote) +} + +const char *csd_filter_to_str(const struct csd_filter *filter, const struct sdp_msg *result, const struct sdp_msg *remote) +{ + return csd_filter_to_str_c(OTC_SELECT, filter, result, remote); +} diff --git a/src/libmsc/db.c b/src/libmsc/db.c index 241bc866e..d12f04c13 100644 --- a/src/libmsc/db.c +++ b/src/libmsc/db.c @@ -1,7 +1,7 @@ -/* Simple HLR/VLR database backend using dbi */ +/* Simple HLR/VLR database backend using sqlite3 */ /* (C) 2008 by Jan Luebbe <jluebbe@debian.org> * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> - * (C) 2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009,2022 by Harald Welte <laforge@gnumonks.org> * All Rights Reserved * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,7 @@ #include <string.h> #include <errno.h> #include <time.h> -#include <dbi/dbi.h> +#include <sqlite3.h> #include <osmocom/msc/gsm_data.h> #include <osmocom/msc/gsm_subscriber.h> @@ -43,12 +43,36 @@ #include <osmocom/core/rate_ctr.h> #include <osmocom/core/utils.h> -static char *db_basename = NULL; -static char *db_dirname = NULL; -static dbi_conn conn; -static dbi_inst inst; +enum stmt_idx { + DB_STMT_SMS_STORE, + DB_STMT_SMS_GET, + DB_STMT_SMS_GET_NEXT_UNSENT, + DB_STMT_SMS_GET_UNSENT_FOR_SUBSCR, + DB_STMT_SMS_GET_NEXT_UNSENT_RR_MSISDN, + DB_STMT_SMS_MARK_DELIVERED, + DB_STMT_SMS_INC_DELIVER_ATTEMPTS, + DB_STMT_SMS_DEL_BY_MSISDN, + DB_STMT_SMS_DEL_BY_ID, + DB_STMT_SMS_DEL_EXPIRED, + DB_STMT_SMS_GET_VALID_UNTIL_BY_ID, + DB_STMT_SMS_GET_OLDEST_EXPIRED, + _NUM_DB_STMT +}; + +struct db_context { + char *fname; + sqlite3 *db; + sqlite3_stmt *stmt[_NUM_DB_STMT]; +}; + +static struct db_context *g_dbc; + -#define SCHEMA_REVISION "5" +/*********************************************************************** + * DATABASE SCHEMA AND MIGRATION + ***********************************************************************/ + +#define SCHEMA_REVISION "6" enum { SCHEMA_META, @@ -181,598 +205,540 @@ static const char *create_stmts[] = { ")", }; -static inline int next_row(dbi_result result) +/*********************************************************************** + * PREPARED STATEMENTS + ***********************************************************************/ + +/* don't change this order as the code assumes this ordering when dereferencing + * database query results! */ +#define SEL_COLUMNS \ + "id," \ + "strftime('%s',created)," \ + "sent," \ + "deliver_attempts," \ + "strftime('%s', valid_until)," \ + "reply_path_req," \ + "status_rep_req," \ + "is_report," \ + "msg_ref," \ + "protocol_id," \ + "data_coding_scheme," \ + "ud_hdr_ind," \ + "src_addr," \ + "src_ton," \ + "src_npi," \ + "dest_addr," \ + "dest_ton," \ + "dest_npi," \ + "user_data," \ + "header," \ + "text" + +enum db_sms_column_idx { + COL_ID, + COL_CREATED, + COL_SENT, + COL_DELIVER_ATTEMPTS, + COL_VALID_UNTIL, + COL_REPLY_PATH_REQ, + COL_STATUS_REP_REQ, + COL_IS_REPORT, + COL_MSG_REF, + COL_PROTOCOL_ID, + COL_DATA_CODING_SCHEME, + COL_UD_HDR_IND, + COL_SRC_ADDR, + COL_SRC_TON, + COL_SRC_NPI, + COL_DEST_ADDR, + COL_DEST_TON, + COL_DEST_NPI, + COL_USER_DATA, + COL_HEADER, + COL_TEXT, +}; + +static const char *stmt_sql[] = { + [DB_STMT_SMS_STORE] = + "INSERT INTO SMS " + "(created, valid_until, reply_path_req, status_rep_req, is_report, " + " msg_ref, protocol_id, data_coding_scheme, ud_hdr_ind, user_data, text, " + " dest_addr, dest_ton, dest_npi, src_addr, src_ton, src_npi) " + "VALUES " + "(datetime($created, 'unixepoch'), datetime($valid_until, 'unixepoch'), " + "$reply_path_req, $status_rep_req, $is_report, " + "$msg_ref, $protocol_id, $data_coding_scheme, $ud_hdr_ind, $user_data, $text, " + "$dest_addr, $dest_ton, $dest_npi, $src_addr, $src_ton, $src_npi)", + [DB_STMT_SMS_GET] = "SELECT " SEL_COLUMNS " FROM SMS WHERE SMS.id = $id", + [DB_STMT_SMS_GET_NEXT_UNSENT] = + "SELECT " SEL_COLUMNS " FROM SMS" + " WHERE sent IS NULL" + " AND id >= $id" + " AND deliver_attempts <= $attempts" + " ORDER BY id LIMIT 1", + [DB_STMT_SMS_GET_UNSENT_FOR_SUBSCR] = + "SELECT " SEL_COLUMNS " FROM SMS" + " WHERE sent IS NULL" + " AND dest_addr = $dest_addr" + " AND deliver_attempts <= $attempts" + " ORDER BY id LIMIT 1", + [DB_STMT_SMS_GET_NEXT_UNSENT_RR_MSISDN] = + "SELECT " SEL_COLUMNS " FROM SMS" + " WHERE sent IS NULL" + " AND dest_addr > $dest_addr" + " AND deliver_attempts <= $attempts" + " ORDER BY dest_addr, id LIMIT 1", + [DB_STMT_SMS_MARK_DELIVERED] = + "UPDATE SMS " + " SET sent = datetime('now') " + " WHERE id = $id", + [DB_STMT_SMS_INC_DELIVER_ATTEMPTS] = + "UPDATE SMS " + " SET deliver_attempts = deliver_attempts + 1 " + " WHERE id = $id", + [DB_STMT_SMS_DEL_BY_MSISDN] = + "DELETE FROM SMS WHERE src_addr=$src_addr OR dest_addr=$dest_addr", + [DB_STMT_SMS_DEL_BY_ID] = + "DELETE FROM SMS WHERE id = $id AND sent is NOT NULL", + [DB_STMT_SMS_DEL_EXPIRED] = + "DELETE FROM SMS WHERE id = $id", + [DB_STMT_SMS_GET_VALID_UNTIL_BY_ID] = + "SELECT strftime('%s', valid_until) FROM SMS WHERE id = $id", + [DB_STMT_SMS_GET_OLDEST_EXPIRED] = + "SELECT id, strftime('%s', valid_until) FROM SMS ORDER BY valid_until LIMIT 1", +}; + +/*********************************************************************** + * libsqlite3 helpers + ***********************************************************************/ + +/* libsqlite3 call-back for error logging */ +static void sql3_error_log_cb(void *arg, int err_code, const char *msg) { - if (!dbi_result_has_next_row(result)) - return 0; - return dbi_result_next_row(result); + LOGP(DDB, LOGL_ERROR, "SQLITE3: (%d) %s\n", err_code, msg); + osmo_log_backtrace(DDB, LOGL_ERROR); } -void db_error_func(dbi_conn conn, void *data) +/* libsqlite3 call-back for normal logging */ +static void sql3_sql_log_cb(void *arg, sqlite3 *s3, const char *stmt, int type) { - const char *msg; - dbi_conn_error(conn, &msg); - LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg); - osmo_log_backtrace(DDB, LOGL_ERROR); + switch (type) { + case 0: + LOGP(DDB, LOGL_DEBUG, "Opened database\n"); + break; + case 1: + LOGP(DDB, LOGL_DEBUG, "%s\n", stmt); + break; + case 2: + LOGP(DDB, LOGL_DEBUG, "Closed database\n"); + break; + default: + LOGP(DDB, LOGL_DEBUG, "Unknown %d\n", type); + break; + } } -static int update_db_revision_2(void) +/* remove statement bindings and reset statement to be re-executed */ +static void db_remove_reset(sqlite3_stmt *stmt) { - dbi_result result; + sqlite3_clear_bindings(stmt); + /* sqlite3_reset() just repeats an error code already evaluated during sqlite3_step(). */ + /* coverity[CHECKED_RETURN] */ + sqlite3_reset(stmt); +} - result = dbi_conn_query(conn, - "ALTER TABLE Subscriber " - "ADD COLUMN expire_lu " - "TIMESTAMP DEFAULT NULL"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to alter table Subscriber (upgrade from rev 2).\n"); - return -EINVAL; +/** bind blob arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +static bool db_bind_blob(sqlite3_stmt *stmt, const char *param_name, + const uint8_t *blob, size_t blob_len) +{ + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; } - dbi_result_free(result); - - result = dbi_conn_query(conn, - "UPDATE Meta " - "SET value = '3' " - "WHERE key = 'revision'"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to update DB schema revision (upgrade from rev 2).\n"); - return -EINVAL; + rc = sqlite3_bind_blob(stmt, idx, blob, blob_len, SQLITE_STATIC); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding blob to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; } - dbi_result_free(result); - - return 0; + return true; } -static void parse_tp_ud_from_result(struct gsm_sms *sms, dbi_result result) +/** bind text arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +static bool db_bind_text(sqlite3_stmt *stmt, const char *param_name, const char *text) { - const unsigned char *user_data; - unsigned int user_data_len; - unsigned int text_len; - const char *text; - - /* Retrieve TP-UDL (User-Data-Length) in octets (regardless of DCS) */ - user_data_len = dbi_result_get_field_length(result, "user_data"); - if (user_data_len > sizeof(sms->user_data)) { - LOGP(DDB, LOGL_ERROR, - "SMS TP-UD length %u is too big, truncating to %zu\n", - user_data_len, sizeof(sms->user_data)); - user_data_len = (uint8_t) sizeof(sms->user_data); + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; } - sms->user_data_len = user_data_len; - - /* Retrieve the TP-UD (User-Data) itself */ - if (user_data_len > 0) { - user_data = dbi_result_get_binary(result, "user_data"); - memcpy(sms->user_data, user_data, user_data_len); + rc = sqlite3_bind_text(stmt, idx, text, -1, SQLITE_STATIC); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding text to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; } + return true; +} - /* Retrieve the text length (excluding '\0') */ - text_len = dbi_result_get_field_length(result, "text"); - if (text_len >= sizeof(sms->text)) { - LOGP(DDB, LOGL_ERROR, - "SMS text length %u is too big, truncating to %zu\n", - text_len, sizeof(sms->text) - 1); - /* OSMO_STRLCPY_ARRAY() does truncation for us */ +/** bind int arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +static bool db_bind_int(sqlite3_stmt *stmt, const char *param_name, int nr) +{ + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; } - - /* Retrieve the text parsed from TP-UD (User-Data) */ - text = dbi_result_get_string(result, "text"); - if (text) - OSMO_STRLCPY_ARRAY(sms->text, text); + rc = sqlite3_bind_int(stmt, idx, nr); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding int64 to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; + } + return true; } -/** - * Copied from the normal sms_from_result_v3 to avoid having - * to make sure that the real routine will remain backward - * compatible. - */ -static struct gsm_sms *sms_from_result_v3(dbi_result result) +/** bind int64 arg and do proper cleanup in case of failure. If param_name is + * NULL, bind to the first parameter (useful for SQL statements that have only + * one parameter). */ +static bool db_bind_int64(sqlite3_stmt *stmt, const char *param_name, int64_t nr) { - struct gsm_sms *sms = sms_alloc(); - long long unsigned int sender_id; - const char *daddr; - char buf[32]; - char *quoted; - dbi_result result2; - const char *extension; - - if (!sms) - return NULL; - - sms->id = dbi_result_get_ulonglong(result, "id"); - - /* find extension by id, assuming that the subscriber still exists in - * the db */ - sender_id = dbi_result_get_ulonglong(result, "sender_id"); - snprintf(buf, sizeof(buf), "%llu", sender_id); - - dbi_conn_quote_string_copy(conn, buf, "ed); - result2 = dbi_conn_queryf(conn, - "SELECT extension FROM Subscriber " - "WHERE id = %s ", quoted); - free(quoted); - extension = dbi_result_get_string(result2, "extension"); - if (extension) - OSMO_STRLCPY_ARRAY(sms->src.addr, extension); - dbi_result_free(result2); - /* got the extension */ - - sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req"); - sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req"); - sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind"); - sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id"); - sms->data_coding_scheme = dbi_result_get_ulonglong(result, - "data_coding_scheme"); - - daddr = dbi_result_get_string(result, "dest_addr"); - if (daddr) - OSMO_STRLCPY_ARRAY(sms->dst.addr, daddr); - - /* Parse TP-UD, TP-UDL and decoded text */ - parse_tp_ud_from_result(sms, result); - - return sms; + int rc; + int idx = param_name ? sqlite3_bind_parameter_index(stmt, param_name) : 1; + if (idx < 1) { + LOGP(DDB, LOGL_ERROR, "Error composing SQL, cannot bind parameter '%s'\n", + param_name); + return false; + } + rc = sqlite3_bind_int64(stmt, idx, nr); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Error binding int64 to SQL parameter %s: %d\n", + param_name ? param_name : "#1", rc); + db_remove_reset(stmt); + return false; + } + return true; } -static int update_db_revision_3(void) +/* callback for sqlite3_exec() below */ +static int db_rev_exec_cb(void *priv, int num_cols, char **vals, char **names) { - dbi_result result; - struct gsm_sms *sms; + char **rev_s = priv; + OSMO_ASSERT(!strcmp(names[0], "value")); + *rev_s = talloc_strdup(NULL, vals[0]); + return 0; +} - LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 3\n"); +static int check_db_revision(struct db_context *dbc) +{ + char *errstr = NULL; + char *rev_s; + int db_rev = 0; + int rc; - result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to begin transaction (upgrade from rev 3)\n"); + /* Make a query */ + rc = sqlite3_exec(dbc->db, "SELECT value FROM Meta WHERE key = 'revision'", + db_rev_exec_cb, &rev_s, &errstr); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Cannot execute SELECT value from META: %s\n", errstr); + sqlite3_free(errstr); return -EINVAL; } - dbi_result_free(result); - - /* Rename old SMS table to be able create a new one */ - result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_3"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to rename the old SMS table (upgrade from rev 3).\n"); - goto rollback; - } - dbi_result_free(result); - /* Create new SMS table with all the bells and whistles! */ - result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to create a new SMS table (upgrade from rev 3).\n"); - goto rollback; + if (!strcmp(rev_s, SCHEMA_REVISION)) { + /* Everything is fine */ + talloc_free(rev_s); + return 0; } - dbi_result_free(result); - /* Cycle through old messages and convert them to the new format */ - result = dbi_conn_query(conn, "SELECT * FROM SMS_3"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed fetch messages from the old SMS table (upgrade from rev 3).\n"); - goto rollback; - } - while (next_row(result)) { - sms = sms_from_result_v3(result); - if (db_sms_store(sms) != 0) { - LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 3).\n"); - sms_free(sms); - dbi_result_free(result); - goto rollback; - } - sms_free(sms); - } - dbi_result_free(result); + LOGP(DDB, LOGL_NOTICE, "Detected DB Revision %s, expected %s\n", rev_s, SCHEMA_REVISION); - /* Remove the temporary table */ - result = dbi_conn_query(conn, "DROP TABLE SMS_3"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to drop the old SMS table (upgrade from rev 3).\n"); - goto rollback; - } - dbi_result_free(result); - - /* We're done. Bump DB Meta revision to 4 */ - result = dbi_conn_query(conn, - "UPDATE Meta " - "SET value = '4' " - "WHERE key = 'revision'"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to update DB schema revision (upgrade from rev 3).\n"); - goto rollback; - } - dbi_result_free(result); + db_rev = atoi(rev_s); + talloc_free(rev_s); - result = dbi_conn_query(conn, "COMMIT TRANSACTION"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to commit the transaction (upgrade from rev 3)\n"); + /* Incremental migration waterfall */ + switch (db_rev) { + case 2: + case 3: + case 4: + LOGP(DDB, LOGL_FATAL, "You must use osmo-msc 1.1.0 to 1.8.0 to upgrade database " + "schema from '%u' to '5', sorry\n", db_rev); + break; + case 5: + LOGP(DDB, LOGL_FATAL, "The storage format of BINARY data in the database " + "has changed. In order to deliver any pending SMS in your database, " + "you must manually convert your database from " + "'%u' to '6'. Alternatively you can use a fresh, blank database " + "with this version of osmo-msc, sorry.\n", db_rev); + return -1; + break; + default: + LOGP(DDB, LOGL_FATAL, "Invalid database schema revision '%d'.\n", db_rev); return -EINVAL; - } else { - dbi_result_free(result); } - /* Shrink DB file size by actually wiping out SMS_3 table data */ - result = dbi_conn_query(conn, "VACUUM"); - if (!result) - LOGP(DDB, LOGL_ERROR, - "VACUUM failed. Ignoring it (upgrade from rev 3).\n"); - else - dbi_result_free(result); - return 0; -rollback: - result = dbi_conn_query(conn, "ROLLBACK TRANSACTION"); - if (!result) - LOGP(DDB, LOGL_ERROR, - "Rollback failed (upgrade from rev 3).\n"); - else - dbi_result_free(result); +//error: + LOGP(DDB, LOGL_FATAL, "Failed to update database from schema revision '%d'.\n", db_rev); + talloc_free(rev_s); + return -EINVAL; } -/* Just like v3, but there is a new message reference field for status reports, - * that is set to zero for existing entries since there is no way we can infer - * this. - */ -static struct gsm_sms *sms_from_result_v4(dbi_result result) -{ - struct gsm_sms *sms = sms_alloc(); - const char *addr; - - if (!sms) - return NULL; - - sms->id = dbi_result_get_ulonglong(result, "id"); - - sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req"); - sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req"); - sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind"); - sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id"); - sms->data_coding_scheme = dbi_result_get_ulonglong(result, - "data_coding_scheme"); - - addr = dbi_result_get_string(result, "src_addr"); - OSMO_STRLCPY_ARRAY(sms->src.addr, addr); - sms->src.ton = dbi_result_get_ulonglong(result, "src_ton"); - sms->src.npi = dbi_result_get_ulonglong(result, "src_npi"); +/*********************************************************************** + * USER API + ***********************************************************************/ - addr = dbi_result_get_string(result, "dest_addr"); - OSMO_STRLCPY_ARRAY(sms->dst.addr, addr); - sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton"); - sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi"); +int db_init(void *ctx, const char *fname, bool enable_sqlite_logging) +{ + unsigned int i; + int rc; + bool has_sqlite_config_sqllog = false; - /* Parse TP-UD, TP-UDL and decoded text */ - parse_tp_ud_from_result(sms, result); + g_dbc = talloc_zero(ctx, struct db_context); + OSMO_ASSERT(g_dbc); - return sms; -} + /* we are a single-threaded program; we want to avoid all the mutex/etc. overhead */ + sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); -static int update_db_revision_4(void) -{ - dbi_result result; - struct gsm_sms *sms; + LOGP(DDB, LOGL_NOTICE, "Init database connection to '%s' using SQLite3 lib version %s\n", + fname, sqlite3_libversion()); - LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 4\n"); + g_dbc->fname = talloc_strdup(g_dbc, fname); - result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to begin transaction (upgrade from rev 4)\n"); - return -EINVAL; + for (i = 0; i < 0xfffff; i++) { + const char *o = sqlite3_compileoption_get(i); + if (!o) + break; + LOGP(DDB, LOGL_DEBUG, "SQLite3 compiled with '%s'\n", o); + if (!strcmp(o, "ENABLE_SQLLOG")) + has_sqlite_config_sqllog = true; } - dbi_result_free(result); - /* Rename old SMS table to be able create a new one */ - result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_4"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to rename the old SMS table (upgrade from rev 4).\n"); - goto rollback; + if (enable_sqlite_logging) { + rc = sqlite3_config(SQLITE_CONFIG_LOG, sql3_error_log_cb, NULL); + if (rc != SQLITE_OK) + LOGP(DDB, LOGL_NOTICE, "Unable to set SQLite3 error log callback\n"); } - dbi_result_free(result); - /* Create new SMS table with all the bells and whistles! */ - result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to create a new SMS table (upgrade from rev 4).\n"); - goto rollback; + if (has_sqlite_config_sqllog) { + rc = sqlite3_config(SQLITE_CONFIG_SQLLOG, sql3_sql_log_cb, NULL); + if (rc != SQLITE_OK) + LOGP(DDB, LOGL_NOTICE, "Unable to set SQLite3 SQL log callback\n"); + } else { + LOGP(DDB, LOGL_DEBUG, "Not setting SQL log callback:" + " SQLite3 compiled without support for it\n"); } - dbi_result_free(result); - /* Cycle through old messages and convert them to the new format */ - result = dbi_conn_query(conn, "SELECT * FROM SMS_4"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed fetch messages from the old SMS table (upgrade from rev 4).\n"); - goto rollback; - } - while (next_row(result)) { - sms = sms_from_result_v4(result); - if (db_sms_store(sms) != 0) { - LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 4).\n"); - sms_free(sms); - dbi_result_free(result); - goto rollback; - } - sms_free(sms); + rc = sqlite3_open(g_dbc->fname, &g_dbc->db); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to open DB; rc =%d\n", rc); + talloc_free(g_dbc); + return -1; } - dbi_result_free(result); - /* Remove the temporary table */ - result = dbi_conn_query(conn, "DROP TABLE SMS_4"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to drop the old SMS table (upgrade from rev 4).\n"); - goto rollback; + /* enable extended result codes */ + rc = sqlite3_extended_result_codes(g_dbc->db, 1); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to enable SQLite3 extended result codes\n"); + /* non-fatal */ } - dbi_result_free(result); - - /* We're done. Bump DB Meta revision to 4 */ - result = dbi_conn_query(conn, - "UPDATE Meta " - "SET value = '5' " - "WHERE key = 'revision'"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to update DB schema revision (upgrade from rev 4).\n"); - goto rollback; - } - dbi_result_free(result); - result = dbi_conn_query(conn, "COMMIT TRANSACTION"); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to commit the transaction (upgrade from rev 4)\n"); - return -EINVAL; - } else { - dbi_result_free(result); + char *err_msg; + rc = sqlite3_exec(g_dbc->db, "PRAGMA journal_mode=WAL; PRAGMA synchronous = NORMAL;", 0, 0, &err_msg); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to set Write-Ahead Logging: %s\n", err_msg); + sqlite3_free(err_msg); + /* non-fatal */ } - /* Shrink DB file size by actually wiping out SMS_4 table data */ - result = dbi_conn_query(conn, "VACUUM"); - if (!result) - LOGP(DDB, LOGL_ERROR, - "VACUUM failed. Ignoring it (upgrade from rev 4).\n"); - else - dbi_result_free(result); + rc = sqlite3_exec(g_dbc->db, "PRAGMA secure_delete=0;", 0, 0, &err_msg); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to disable SECURE_DELETE: %s\n", err_msg); + sqlite3_free(err_msg); + /* non-fatal */ + } return 0; - -rollback: - result = dbi_conn_query(conn, "ROLLBACK TRANSACTION"); - if (!result) - LOGP(DDB, LOGL_ERROR, - "Rollback failed (upgrade from rev 4).\n"); - else - dbi_result_free(result); - return -EINVAL; } -static int check_db_revision(void) +int db_fini(void) { - dbi_result result; - const char *rev_s; - int db_rev = 0; - - /* Make a query */ - result = dbi_conn_query(conn, - "SELECT value FROM Meta " - "WHERE key = 'revision'"); - - if (!result) - return -EINVAL; - - if (!next_row(result)) { - dbi_result_free(result); - return -EINVAL; - } + unsigned int i; + int rc; - /* Fetch the DB schema revision */ - rev_s = dbi_result_get_string(result, "value"); - if (!rev_s) { - dbi_result_free(result); - return -EINVAL; - } - - if (!strcmp(rev_s, SCHEMA_REVISION)) { - /* Everything is fine */ - dbi_result_free(result); + if (!g_dbc) return 0; - } - - db_rev = atoi(rev_s); - dbi_result_free(result); - /* Incremental migration waterfall */ - switch (db_rev) { - case 2: - if (update_db_revision_2()) - goto error; - /* fall through */ - case 3: - if (update_db_revision_3()) - goto error; - /* fall through */ - case 4: - if (update_db_revision_4()) - goto error; - - /* The end of waterfall */ - break; - default: - LOGP(DDB, LOGL_FATAL, - "Invalid database schema revision '%d'.\n", db_rev); - return -EINVAL; + for (i = 0; i < ARRAY_SIZE(g_dbc->stmt); i++) { + /* it is ok to call finalize on NULL */ + sqlite3_finalize(g_dbc->stmt[i]); } - return 0; - -error: - LOGP(DDB, LOGL_FATAL, "Failed to update database " - "from schema revision '%d'.\n", db_rev); - return -EINVAL; -} - -static int db_configure(void) -{ - dbi_result result; + /* Ask sqlite3 to close DB */ + rc = sqlite3_close(g_dbc->db); + if (rc != SQLITE_OK) { /* Make sure it's actually closed! */ + LOGP(DDB, LOGL_ERROR, "Couldn't close database: (rc=%d) %s\n", + rc, sqlite3_errmsg(g_dbc->db)); + } - result = dbi_conn_query(conn, - "PRAGMA synchronous = FULL"); - if (!result) - return -EINVAL; + talloc_free(g_dbc); + g_dbc = NULL; - dbi_result_free(result); return 0; } -int db_init(const char *name) +/* run (execute) a series of SQL statements */ +static int db_run_statements(struct db_context *dbc, const char **statements, size_t statements_count) { - dbi_initialize_r(NULL, &inst); - - LOGP(DDB, LOGL_NOTICE, "Init database connection to '%s' using %s\n", - name, dbi_version()); - - conn = dbi_conn_new_r("sqlite3", inst); - if (conn == NULL) { - LOGP(DDB, LOGL_FATAL, "Failed to create database connection to sqlite3 db '%s'; " - "Is the sqlite3 database driver for libdbi installed on this system?\n", name); - return 1; + int i; + for (i = 0; i < statements_count; i++) { + const char *stmt_str = statements[i]; + char *errmsg = NULL; + int rc; + + rc = sqlite3_exec(dbc->db, stmt_str, NULL, NULL, &errmsg); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "SQL error during SQL statement '%s': %s\n", stmt_str, errmsg); + sqlite3_free(errmsg); + return -1; + } } - - dbi_conn_error_handler( conn, db_error_func, NULL ); - - /* MySQL - dbi_conn_set_option(conn, "host", "localhost"); - dbi_conn_set_option(conn, "username", "your_name"); - dbi_conn_set_option(conn, "password", "your_password"); - dbi_conn_set_option(conn, "dbname", "your_dbname"); - dbi_conn_set_option(conn, "encoding", "UTF-8"); - */ - - /* SqLite 3 */ - db_basename = strdup(name); - db_dirname = strdup(name); - dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname)); - dbi_conn_set_option(conn, "dbname", basename(db_basename)); - - if (dbi_conn_connect(conn) < 0) - goto out_err; - return 0; - -out_err: - free(db_dirname); - free(db_basename); - db_dirname = db_basename = NULL; - return -1; } - int db_prepare(void) { - dbi_result result; - int i; + unsigned int i; + int rc; - for (i = 0; i < ARRAY_SIZE(create_stmts); i++) { - result = dbi_conn_query(conn, create_stmts[i]); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to create some table.\n"); - return 1; - } - dbi_result_free(result); + OSMO_ASSERT(g_dbc); + rc = db_run_statements(g_dbc, create_stmts, ARRAY_SIZE(create_stmts)); + if (rc < 0) { + LOGP(DDB, LOGL_ERROR, "Failed to create some table.\n"); + return 1; } - if (check_db_revision() < 0) { + if (check_db_revision(g_dbc) < 0) { LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, " "please update your database schema\n"); return -1; } - db_configure(); - - return 0; -} - -int db_fini(void) -{ - dbi_conn_close(conn); - dbi_shutdown_r(inst); + /* prepare all SQL statements */ + for (i = 0; i < ARRAY_SIZE(g_dbc->stmt); i++) { + rc = sqlite3_prepare_v2(g_dbc->db, stmt_sql[i], -1, + &g_dbc->stmt[i], NULL); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", stmt_sql[i]); + return -1; + } + } - free(db_dirname); - free(db_basename); return 0; } /* store an [unsent] SMS to the database */ int db_sms_store(struct gsm_sms *sms) { - dbi_result result; - char *q_text, *q_daddr, *q_saddr; - unsigned char *q_udata = NULL; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_STORE]; time_t now, validity_timestamp; - - dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text); - dbi_conn_quote_string_copy(conn, (char *)sms->dst.addr, &q_daddr); - dbi_conn_quote_string_copy(conn, (char *)sms->src.addr, &q_saddr); - - /* Guard against zero-length input, as this may cause - * buffer overruns in libdbi / libdbdsqlite3. */ - if (sms->user_data_len > 0) { - dbi_conn_quote_binary_copy(conn, sms->user_data, - sms->user_data_len, - &q_udata); - } + int rc; now = time(NULL); validity_timestamp = now + sms->validity_minutes * 60; - result = dbi_conn_queryf(conn, - "INSERT INTO SMS " - "(created, valid_until, " - "reply_path_req, status_rep_req, is_report, " - "msg_ref, protocol_id, data_coding_scheme, " - "ud_hdr_ind, " - "user_data, text, " - "dest_addr, dest_ton, dest_npi, " - "src_addr, src_ton, src_npi) VALUES " - "(datetime('%lld', 'unixepoch'), datetime('%lld', 'unixepoch'), " - "%u, %u, %u, " - "%u, %u, %u, " - "%u, " - "%s, %s, " - "%s, %u, %u, " - "%s, %u, %u)", - (int64_t)now, (int64_t)validity_timestamp, - sms->reply_path_req, sms->status_rep_req, sms->is_report, - sms->msg_ref, sms->protocol_id, sms->data_coding_scheme, - sms->ud_hdr_ind, - q_udata, q_text, - q_daddr, sms->dst.ton, sms->dst.npi, - q_saddr, sms->src.ton, sms->src.npi); - free(q_text); - free(q_udata); - free(q_daddr); - free(q_saddr); - - if (!result) + db_bind_int64(stmt, "$created", (int64_t) now); + db_bind_int64(stmt, "$valid_until", (int64_t) validity_timestamp); + db_bind_int(stmt, "$reply_path_req", sms->reply_path_req); + db_bind_int(stmt, "$status_rep_req", sms->status_rep_req); + db_bind_int(stmt, "$is_report", sms->is_report); + db_bind_int(stmt, "$msg_ref", sms->msg_ref); + db_bind_int(stmt, "$protocol_id", sms->protocol_id); + db_bind_int(stmt, "$data_coding_scheme", sms->data_coding_scheme); + db_bind_int(stmt, "$ud_hdr_ind", sms->ud_hdr_ind); + /* FIXME: do we need to use legacy DBI compatible quoting of sms->user_data? */ + db_bind_blob(stmt, "$user_data", sms->user_data, sms->user_data_len); + db_bind_text(stmt, "$text", (char *)sms->text); + db_bind_text(stmt, "$dest_addr", (char *)sms->dst.addr); + db_bind_int(stmt, "$dest_ton", sms->dst.ton); + db_bind_int(stmt, "$dest_npi", sms->dst.npi); + db_bind_text(stmt, "$src_addr", (char *)sms->src.addr); + db_bind_int(stmt, "$src_ton", sms->src.ton); + db_bind_int(stmt, "$src_npi", sms->src.npi); + + /* execute statement */ + rc = sqlite3_step(stmt); + db_remove_reset(stmt); + if (rc != SQLITE_DONE) { + LOGP(DDB, LOGL_ERROR, "Cannot create SMS: SQL error: (%d) %s\n", rc, sqlite3_errmsg(g_dbc->db)); return -EIO; + } + + sms->id = sqlite3_last_insert_rowid(g_dbc->db); + + LOGP(DLSMS, LOGL_INFO, "Stored SMS id=%llu in DB\n", sms->id); - dbi_result_free(result); return 0; } -static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result) +static void parse_tp_ud_from_result(struct gsm_sms *sms, sqlite3_stmt *stmt) +{ + const unsigned char *user_data; + unsigned int user_data_len; + unsigned int text_len; + const char *text; + + /* Retrieve TP-UDL (User-Data-Length) in octets (regardless of DCS) */ + user_data_len = sqlite3_column_bytes(stmt, COL_USER_DATA); + if (user_data_len > sizeof(sms->user_data)) { + LOGP(DDB, LOGL_ERROR, + "SMS TP-UD length %u is too big, truncating to %zu\n", + user_data_len, sizeof(sms->user_data)); + user_data_len = (uint8_t) sizeof(sms->user_data); + } + sms->user_data_len = user_data_len; + + /* Retrieve the TP-UD (User-Data) itself */ + if (user_data_len > 0) { + user_data = sqlite3_column_blob(stmt, COL_USER_DATA); + memcpy(sms->user_data, user_data, user_data_len); + } + + /* Retrieve the text length (excluding '\0') */ + text_len = sqlite3_column_bytes(stmt, COL_TEXT); + if (text_len >= sizeof(sms->text)) { + LOGP(DDB, LOGL_ERROR, + "SMS text length %u is too big, truncating to %zu\n", + text_len, sizeof(sms->text) - 1); + /* OSMO_STRLCPY_ARRAY() does truncation for us */ + } + + /* Retrieve the text parsed from TP-UD (User-Data) */ + text = (const char *)sqlite3_column_text(stmt, COL_TEXT); + if (text) + OSMO_STRLCPY_ARRAY(sms->text, text); +} + +static struct gsm_sms *sms_from_result(struct gsm_network *net, sqlite3_stmt *stmt) { struct gsm_sms *sms = sms_alloc(); const char *daddr, *saddr; @@ -781,24 +747,23 @@ static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result resul if (!sms) return NULL; - sms->id = dbi_result_get_ulonglong(result, "id"); + sms->id = sqlite3_column_int64(stmt, COL_ID); + + sms->created = sqlite3_column_int64(stmt, COL_CREATED); + validity_timestamp = sqlite3_column_int64(stmt, COL_VALID_UNTIL); - sms->created = dbi_result_get_datetime(result, "created"); - validity_timestamp = dbi_result_get_datetime(result, "valid_until"); sms->validity_minutes = (validity_timestamp - sms->created) / 60; - /* FIXME: those should all be get_uchar, but sqlite3 is braindead */ - sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req"); - sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req"); - sms->is_report = dbi_result_get_ulonglong(result, "is_report"); - sms->msg_ref = dbi_result_get_ulonglong(result, "msg_ref"); - sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind"); - sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id"); - sms->data_coding_scheme = dbi_result_get_ulonglong(result, - "data_coding_scheme"); - - sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi"); - sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton"); - daddr = dbi_result_get_string(result, "dest_addr"); + sms->reply_path_req = sqlite3_column_int(stmt, COL_REPLY_PATH_REQ); + sms->status_rep_req = sqlite3_column_int(stmt, COL_STATUS_REP_REQ); + sms->is_report = sqlite3_column_int(stmt, COL_IS_REPORT); + sms->msg_ref = sqlite3_column_int(stmt, COL_MSG_REF); + sms->ud_hdr_ind = sqlite3_column_int(stmt, COL_UD_HDR_IND); + sms->protocol_id = sqlite3_column_int(stmt, COL_PROTOCOL_ID); + sms->data_coding_scheme = sqlite3_column_int(stmt, COL_DATA_CODING_SCHEME); + + sms->dst.npi = sqlite3_column_int(stmt, COL_DEST_NPI); + sms->dst.ton = sqlite3_column_int(stmt, COL_DEST_TON); + daddr = (const char *)sqlite3_column_text(stmt, COL_DEST_ADDR); if (daddr) OSMO_STRLCPY_ARRAY(sms->dst.addr, daddr); @@ -806,78 +771,72 @@ static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result resul sms->receiver = vlr_subscr_find_by_msisdn(net->vlr, sms->dst.addr, VSUB_USE_SMS_RECEIVER); - sms->src.npi = dbi_result_get_ulonglong(result, "src_npi"); - sms->src.ton = dbi_result_get_ulonglong(result, "src_ton"); - saddr = dbi_result_get_string(result, "src_addr"); + sms->src.npi = sqlite3_column_int(stmt, COL_SRC_NPI); + sms->src.ton = sqlite3_column_int(stmt, COL_SRC_TON); + saddr = (const char *)sqlite3_column_text(stmt, COL_SRC_ADDR); if (saddr) OSMO_STRLCPY_ARRAY(sms->src.addr, saddr); /* Parse TP-UD, TP-UDL and decoded text */ - parse_tp_ud_from_result(sms, result); + parse_tp_ud_from_result(sms, stmt); return sms; } struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id) { - dbi_result result; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_GET]; struct gsm_sms *sms; + int rc; - result = dbi_conn_queryf(conn, - "SELECT * FROM SMS WHERE SMS.id = %llu", id); - if (!result) - return NULL; + db_bind_int64(stmt, "$id", id); - if (!next_row(result)) { - dbi_result_free(result); + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { + db_remove_reset(stmt); return NULL; } - sms = sms_from_result(net, result); - - dbi_result_free(result); + sms = sms_from_result(net, stmt); + db_remove_reset(stmt); return sms; } struct gsm_sms *db_sms_get_next_unsent(struct gsm_network *net, unsigned long long min_sms_id, - unsigned int max_failed) + int max_failed) { - dbi_result result; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_GET_NEXT_UNSENT]; struct gsm_sms *sms; + int rc; - result = dbi_conn_queryf(conn, - "SELECT * FROM SMS" - " WHERE sent IS NULL" - " AND id >= %llu" - " AND deliver_attempts <= %u" - " ORDER BY id LIMIT 1", - min_sms_id, max_failed); + db_bind_int64(stmt, "$id", min_sms_id); + db_bind_int(stmt, "$attempts", max_failed); - if (!result) - return NULL; - - if (!next_row(result)) { - dbi_result_free(result); + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { + db_remove_reset(stmt); return NULL; } - sms = sms_from_result(net, result); - - dbi_result_free(result); + sms = sms_from_result(net, stmt); + db_remove_reset(stmt); return sms; } /* retrieve the next unsent SMS for a given subscriber */ struct gsm_sms *db_sms_get_unsent_for_subscr(struct vlr_subscr *vsub, - unsigned int max_failed) + int max_failed) { + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_GET_UNSENT_FOR_SUBSCR]; struct gsm_network *net = vsub->vlr->user_ctx; - dbi_result result; struct gsm_sms *sms; - char *q_msisdn; + int rc; if (!vsub->lu_complete) return NULL; @@ -886,60 +845,42 @@ struct gsm_sms *db_sms_get_unsent_for_subscr(struct vlr_subscr *vsub, if (*vsub->msisdn == '\0') return NULL; - dbi_conn_quote_string_copy(conn, vsub->msisdn, &q_msisdn); - result = dbi_conn_queryf(conn, - "SELECT * FROM SMS" - " WHERE sent IS NULL" - " AND dest_addr = %s" - " AND deliver_attempts <= %u" - " ORDER BY id LIMIT 1", - q_msisdn, max_failed); - free(q_msisdn); - - if (!result) - return NULL; + db_bind_text(stmt, "$dest_addr", vsub->msisdn); + db_bind_int(stmt, "$attempts", max_failed); - if (!next_row(result)) { - dbi_result_free(result); + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { + db_remove_reset(stmt); return NULL; } - sms = sms_from_result(net, result); - - dbi_result_free(result); + sms = sms_from_result(net, stmt); + db_remove_reset(stmt); return sms; } struct gsm_sms *db_sms_get_next_unsent_rr_msisdn(struct gsm_network *net, const char *last_msisdn, - unsigned int max_failed) + int max_failed) { - dbi_result result; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_GET_NEXT_UNSENT_RR_MSISDN]; struct gsm_sms *sms; - char *q_last_msisdn; + int rc; - dbi_conn_quote_string_copy(conn, last_msisdn, &q_last_msisdn); - result = dbi_conn_queryf(conn, - "SELECT * FROM SMS" - " WHERE sent IS NULL" - " AND dest_addr > %s" - " AND deliver_attempts <= %u" - " ORDER BY dest_addr, id LIMIT 1", - q_last_msisdn, max_failed); - free(q_last_msisdn); - - if (!result) - return NULL; + db_bind_text(stmt, "$dest_addr", last_msisdn); + db_bind_int(stmt, "$attempts", max_failed); - if (!next_row(result)) { - dbi_result_free(result); + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { + db_remove_reset(stmt); return NULL; } - sms = sms_from_result(net, result); + sms = sms_from_result(net, stmt); - dbi_result_free(result); + db_remove_reset(stmt); return sms; } @@ -947,84 +888,100 @@ struct gsm_sms *db_sms_get_next_unsent_rr_msisdn(struct gsm_network *net, /* mark a given SMS as delivered */ int db_sms_mark_delivered(struct gsm_sms *sms) { - dbi_result result; + sqlite3_stmt *stmt; + int rc; - result = dbi_conn_queryf(conn, - "UPDATE SMS " - "SET sent = datetime('now') " - "WHERE id = %llu", sms->id); - if (!result) { + /* this only happens in unit tests that don't db_init() */ + if (!g_dbc) + return 0; + + stmt = g_dbc->stmt[DB_STMT_SMS_MARK_DELIVERED]; + db_bind_int64(stmt, "$id", sms->id); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + db_remove_reset(stmt); LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id); return 1; } - dbi_result_free(result); + db_remove_reset(stmt); return 0; } /* increase the number of attempted deliveries */ int db_sms_inc_deliver_attempts(struct gsm_sms *sms) { - dbi_result result; + sqlite3_stmt *stmt; + int rc; - result = dbi_conn_queryf(conn, - "UPDATE SMS " - "SET deliver_attempts = deliver_attempts + 1 " - "WHERE id = %llu", sms->id); - if (!result) { - LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for " - "SMS %llu.\n", sms->id); + /* this only happens in unit tests that don't db_init() */ + if (!g_dbc) + return 0; + + stmt = g_dbc->stmt[DB_STMT_SMS_INC_DELIVER_ATTEMPTS]; + db_bind_int64(stmt, "$id", sms->id); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + db_remove_reset(stmt); + LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for SMS %llu.\n", sms->id); return 1; } - dbi_result_free(result); + db_remove_reset(stmt); return 0; } /* Drop all pending SMS to or from the given extension */ int db_sms_delete_by_msisdn(const char *msisdn) { - dbi_result result; - char *q_msisdn; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_DEL_BY_MSISDN]; + int rc; + if (!msisdn || !*msisdn) return 0; - dbi_conn_quote_string_copy(conn, msisdn, &q_msisdn); - result = dbi_conn_queryf(conn, - "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s", - q_msisdn, q_msisdn); - free(q_msisdn); + db_bind_text(stmt, "$src_addr", msisdn); + db_bind_text(stmt, "$dest_addr", msisdn); - if (!result) { - LOGP(DDB, LOGL_ERROR, - "Failed to delete SMS for %s\n", msisdn); + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + db_remove_reset(stmt); + LOGP(DDB, LOGL_ERROR, "Failed to delete SMS for %s\n", msisdn); return -1; } - dbi_result_free(result); + + db_remove_reset(stmt); return 0; } int db_sms_delete_sent_message_by_id(unsigned long long sms_id) { - dbi_result result; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_DEL_BY_ID]; + int rc; + + db_bind_int64(stmt, "$id", sms_id); - result = dbi_conn_queryf(conn, - "DELETE FROM SMS WHERE id = %llu AND sent is NOT NULL", - sms_id); - if (!result) { + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + db_remove_reset(stmt); LOGP(DDB, LOGL_ERROR, "Failed to delete SMS %llu.\n", sms_id); return 1; } - dbi_result_free(result); + db_remove_reset(stmt); return 0; } - static int delete_expired_sms(unsigned long long sms_id, time_t validity_timestamp) { - dbi_result result; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_DEL_EXPIRED]; time_t now; + int rc; now = time(NULL); @@ -1032,51 +989,55 @@ static int delete_expired_sms(unsigned long long sms_id, time_t validity_timesta if (validity_timestamp > now) return -1; - result = dbi_conn_queryf(conn, "DELETE FROM SMS WHERE id = %llu", sms_id); - if (!result) { + db_bind_int64(stmt, "$id", sms_id); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + db_remove_reset(stmt); LOGP(DDB, LOGL_ERROR, "Failed to delete SMS %llu.\n", sms_id); return -1; } - dbi_result_free(result); + + db_remove_reset(stmt); return 0; } int db_sms_delete_expired_message_by_id(unsigned long long sms_id) { - dbi_result result; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_GET_VALID_UNTIL_BY_ID]; time_t validity_timestamp; + int rc; - result = dbi_conn_queryf(conn, "SELECT valid_until FROM SMS WHERE id = %llu", sms_id); - if (!result) - return -1; - if (!next_row(result)) { - dbi_result_free(result); + db_bind_int64(stmt, "$id", sms_id); + + rc = sqlite3_step(stmt); + if (rc != SQLITE_ROW) { + db_remove_reset(stmt); return -1; } - validity_timestamp = dbi_result_get_datetime(result, "valid_until"); + validity_timestamp = sqlite3_column_int64(stmt, 0); - dbi_result_free(result); + db_remove_reset(stmt); return delete_expired_sms(sms_id, validity_timestamp); } void db_sms_delete_oldest_expired_message(void) { - dbi_result result; - - result = dbi_conn_queryf(conn, "SELECT id,valid_until FROM SMS " - "ORDER BY valid_until LIMIT 1"); - if (!result) - return; + OSMO_ASSERT(g_dbc); + sqlite3_stmt *stmt = g_dbc->stmt[DB_STMT_SMS_GET_OLDEST_EXPIRED]; + int rc; - if (next_row(result)) { + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) { unsigned long long sms_id; time_t validity_timestamp; - sms_id = dbi_result_get_ulonglong(result, "id"); - validity_timestamp = dbi_result_get_datetime(result, "valid_until"); + sms_id = sqlite3_column_int64(stmt, 0); + validity_timestamp = sqlite3_column_int64(stmt, 1); delete_expired_sms(sms_id, validity_timestamp); } - dbi_result_free(result); + db_remove_reset(stmt); } diff --git a/src/libmsc/e_link.c b/src/libmsc/e_link.c index 0a2be795c..b26f53bc5 100644 --- a/src/libmsc/e_link.c +++ b/src/libmsc/e_link.c @@ -1,6 +1,6 @@ /* E-interface messaging over a GSUP connection */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c index 0bdc4fbe8..17350faad 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -36,6 +36,7 @@ #include <osmocom/msc/debug.h> #include <osmocom/msc/gsm_data.h> #include <osmocom/msc/gsm_04_08.h> +#include <osmocom/msc/msc_vgcs.h> #include <osmocom/msc/signal.h> #include <osmocom/msc/transaction.h> #include <osmocom/msc/vlr.h> @@ -48,11 +49,14 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> #include <osmocom/core/byteswap.h> +#include <osmocom/core/fsm.h> #include <osmocom/gsm/tlv.h> #include <osmocom/crypt/auth.h> +#include <osmocom/crypt/utran_cipher.h> #include <osmocom/msc/msub.h> #include <osmocom/msc/msc_roles.h> +#include <osmocom/msc/call_leg.h> #include <assert.h> @@ -116,42 +120,44 @@ static int gsm0408_loc_upd_acc(struct msc_a *msc_a, uint32_t send_tmsi) struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD ACC"); struct gsm48_hdr *gh; struct gsm48_loc_area_id *lai; - uint8_t *mid; - struct gsm_network *net = msc_a_net(msc_a); struct vlr_subscr *vsub = msc_a_vsub(msc_a); - struct osmo_location_area_id laid = { - .plmn = net->plmn, - .lac = vsub->cgi.lai.lac, - }; + uint8_t *l; + int rc; + struct osmo_mobile_identity mi = {}; gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); gh->proto_discr = GSM48_PDISC_MM; gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT; lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai)); - gsm48_generate_lai2(lai, &laid); + gsm48_generate_lai2(lai, &vsub->cgi.lai); if (send_tmsi == GSM_RESERVED_TMSI) { /* we did not allocate a TMSI to the MS, so we need to * include the IMSI in order for the MS to delete any * old TMSI that might still be allocated */ - uint8_t mi[10]; - int len; - len = gsm48_generate_mid_from_imsi(mi, vsub->imsi); - mid = msgb_put(msg, len); - memcpy(mid, mi, len); + mi.type = GSM_MI_TYPE_IMSI; + OSMO_STRLCPY_ARRAY(mi.imsi, vsub->imsi); DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT\n", vlr_subscr_name(vsub)); } else { /* Include the TMSI, which means that the MS will send a * TMSI REALLOCATION COMPLETE, and we should wait for * that until T3250 expiration */ - mid = msgb_put(msg, GSM48_MID_TMSI_LEN); - gsm48_generate_mid_from_tmsi(mid, send_tmsi); + mi.type = GSM_MI_TYPE_TMSI; + mi.tmsi = send_tmsi; DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT (TMSI = 0x%08x)\n", vlr_subscr_name(vsub), send_tmsi); } + l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID); + rc = osmo_mobile_identity_encode_msgb(msg, &mi, false); + if (rc < 0) { + msgb_free(msg); + return -EINVAL; + } + *l = rc; + /* TODO: Follow-on proceed */ /* TODO: CTS permission */ /* TODO: Equivalent PLMNs */ @@ -180,9 +186,11 @@ static int mm_tx_identity_req(struct msc_a *msc_a, uint8_t id_type) static int mm_rx_id_resp(struct msc_a *msc_a, struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); - uint8_t *mi = gh->data+1; + uint8_t *mi_data = gh->data+1; uint8_t mi_len = gh->data[0]; struct vlr_subscr *vsub = msc_a_vsub(msc_a); + struct osmo_mobile_identity mi; + int rc; if (!vsub) { LOGP(DMM, LOGL_ERROR, @@ -190,11 +198,33 @@ static int mm_rx_id_resp(struct msc_a *msc_a, struct msgb *msg) return -EINVAL; } - DEBUGP(DMM, "IDENTITY RESPONSE: MI=%s\n", osmo_mi_name(mi, mi_len)); + /* There muct be at least one octet with MI type */ + if (!mi_len) { + LOGP(DMM, LOGL_NOTICE, "MM Identity Response contains " + "malformed Mobile Identity\n"); + return -EINVAL; + } + + rc = osmo_mobile_identity_decode(&mi, mi_data, mi_len, false); + if (rc) { + LOGP(DMM, LOGL_ERROR, "Failure to decode Mobile Identity in MM Identity Response (rc=%d)\n", rc); + return -EINVAL; + } + + /* Make sure we got what we expected */ + if (mi.type != msc_a->mm_id_req_type) { + LOGP(DMM, LOGL_NOTICE, "MM Identity Response contains unexpected " + "Mobile Identity type %s (extected %s)\n", + gsm48_mi_type_name(mi.type), + gsm48_mi_type_name(msc_a->mm_id_req_type)); + return -EINVAL; + } + + DEBUGP(DMM, "IDENTITY RESPONSE: %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data); - return vlr_subscr_rx_id_resp(vsub, mi, mi_len); + return vlr_subscr_rx_id_resp(vsub, &mi); } /* 9.2.5 CM service accept */ @@ -212,7 +242,9 @@ static int msc_gsm48_tx_mm_serv_ack(struct msc_a *msc_a) return msc_a_tx_dtap_to_i(msc_a, msg); } -/* 9.2.6 CM service reject */ +/* 9.2.6 CM service reject. + * For an active and valid CM Service Request, instead use msc_vlr_tx_cm_serv_rej(), which also takes care of + * decrementing the use token for that service type. */ static int msc_gsm48_tx_mm_serv_rej(struct msc_a *msc_a, enum gsm48_reject_value value) { @@ -288,8 +320,6 @@ static int mm_rx_loc_upd_req(struct msc_a *msc_a, struct msgb *msg) { struct gsm48_hdr *gh = msgb_l3(msg); struct gsm48_loc_upd_req *lu; - uint8_t mi_type; - char mi_string[GSM48_MI_SIZE]; enum vlr_lu_type vlr_lu_type = VLR_LU_TYPE_REGULAR; uint32_t tmsi; char *imsi; @@ -298,6 +328,15 @@ static int mm_rx_loc_upd_req(struct msc_a *msc_a, struct msgb *msg) bool is_utran; struct gsm_network *net = msc_a_net(msc_a); struct vlr_subscr *vsub; + struct osmo_mobile_identity mi; + int rc; + + rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false); + if (rc) { + LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, + "Failed to decode Mobile Identity in Location Updating Request\n"); + return -EINVAL; + } lu = (struct gsm48_loc_upd_req *) gh->data; @@ -305,7 +344,7 @@ static int mm_rx_loc_upd_req(struct msc_a *msc_a, struct msgb *msg) LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "Cannot accept another LU, conn already busy establishing authenticity;" " extraneous LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n", - osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type)); + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi), osmo_lu_type_name(lu->type)); return -EINVAL; } @@ -313,47 +352,46 @@ static int mm_rx_loc_upd_req(struct msc_a *msc_a, struct msgb *msg) LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "Cannot accept another LU, conn already established;" " extraneous LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n", - osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type)); + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi), osmo_lu_type_name(lu->type)); return -EINVAL; } msc_a->complete_layer3_type = COMPLETE_LAYER3_LU; - msub_update_id_from_mi(msc_a->c.msub, lu->mi, lu->mi_len); + + msub_update_id_from_mi(msc_a->c.msub, &mi); LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "LOCATION UPDATING REQUEST: MI=%s LU-type=%s\n", - osmo_mi_name(lu->mi, lu->mi_len), osmo_lu_type_name(lu->type)); + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi), osmo_lu_type_name(lu->type)); - osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len); + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &mi); switch (lu->type) { case GSM48_LUPD_NORMAL: - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_NORMAL)); vlr_lu_type = VLR_LU_TYPE_REGULAR; break; case GSM48_LUPD_IMSI_ATT: - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_ATTACH)); vlr_lu_type = VLR_LU_TYPE_IMSI_ATTACH; break; case GSM48_LUPD_PERIODIC: - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_PERIODIC)); vlr_lu_type = VLR_LU_TYPE_PERIODIC; break; } /* TODO: 10.5.1.6 MS Classmark for UMTS / Classmark 2 */ - /* TODO: 10.5.3.14 Aditional update parameters (CS fallback calls) */ + /* TODO: 10.5.3.14 Additional update parameters (CS fallback calls) */ /* TODO: 10.5.7.8 Device properties */ /* TODO: 10.5.1.15 MS network feature support */ - mi_type = lu->mi[0] & GSM_MI_TYPE_MASK; - gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len); - switch (mi_type) { + switch (mi.type) { case GSM_MI_TYPE_IMSI: tmsi = GSM_RESERVED_TMSI; - imsi = mi_string; + imsi = mi.imsi; break; case GSM_MI_TYPE_TMSI: - tmsi = tmsi_from_string(mi_string); + tmsi = mi.tmsi; imsi = NULL; break; default: @@ -375,7 +413,8 @@ static int mm_rx_loc_upd_req(struct msc_a *msc_a, struct msgb *msg) net->vlr, msc_a, vlr_lu_type, tmsi, imsi, &old_lai, &msc_a->via_cell.lai, is_utran || net->authentication_required, - is_utran ? net->uea_encryption : net->a5_encryption_mask > 0x01, + msc_a_is_ciphering_to_be_attempted(msc_a), + msc_a_is_ciphering_required(msc_a), lu->key_seq, osmo_gsm48_classmark1_is_r99(&lu->classmark1), is_utran, @@ -631,44 +670,36 @@ int gsm48_tx_mm_auth_rej(struct msc_a *msc_a) static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type, enum gsm48_reject_value cause); -static int cm_serv_reuse_conn(struct msc_a *msc_a, const uint8_t *mi_lv, enum osmo_cm_service_type cm_serv_type) +static int cm_serv_reuse_conn(struct msc_a *msc_a, const struct osmo_mobile_identity *mi, enum osmo_cm_service_type cm_serv_type) { - uint8_t mi_type; - char mi_string[GSM48_MI_SIZE]; - uint32_t tmsi; struct gsm_network *net = msc_a_net(msc_a); struct vlr_subscr *vsub = msc_a_vsub(msc_a); - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi_lv+1, mi_lv[0]); - mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; - - switch (mi_type) { + switch (mi->type) { case GSM_MI_TYPE_IMSI: - if (vlr_subscr_matches_imsi(vsub, mi_string)) + if (vlr_subscr_matches_imsi(vsub, mi->imsi)) goto accept_reuse; break; case GSM_MI_TYPE_TMSI: - tmsi = osmo_load32be(mi_lv+2); - if (vlr_subscr_matches_tmsi(vsub, tmsi)) + if (vlr_subscr_matches_tmsi(vsub, mi->tmsi)) goto accept_reuse; break; case GSM_MI_TYPE_IMEI: - if (vlr_subscr_matches_imei(vsub, mi_string)) + if (vlr_subscr_matches_imei(vsub, mi->imei)) goto accept_reuse; break; default: break; } - LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "CM Service Request with mismatching mobile identity: %s %s\n", - gsm48_mi_type_name(mi_type), mi_string); + LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "CM Service Request with mismatching mobile identity: %s\n", + osmo_mobile_identity_to_str_c(OTC_SELECT, mi)); msc_vlr_tx_cm_serv_rej(msc_a, cm_serv_type, GSM48_REJECT_ILLEGAL_MS); return -EINVAL; accept_reuse: LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "re-using already accepted connection\n"); - msc_a_get(msc_a, msc_a_cm_service_type_to_use(cm_serv_type)); msub_update_id(msc_a->c.msub); return net->vlr->ops.tx_cm_serv_acc(msc_a, cm_serv_type); } @@ -676,7 +707,7 @@ accept_reuse: /* * Handle CM Service Requests * a) Verify that the packet is long enough to contain the information - * we require otherwsie reject with INCORRECT_MESSAGE + * we require otherwise reject with INCORRECT_MESSAGE * b) Try to parse the TMSI. If we do not have one reject * c) Check that we know the subscriber with the TMSI otherwise reject * with a HLR cause @@ -691,10 +722,18 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) struct gsm48_service_request *req; struct gsm48_classmark2 *cm2; uint8_t *cm2_buf, cm2_len; - uint8_t *mi_buf, mi_len; - uint8_t *mi, mi_type; bool is_utran; struct vlr_subscr *vsub; + struct osmo_mobile_identity mi; + int rc; + + /* There are two ways to respond with a CM Service Reject: + * Directly and only send the CM Service Reject with msc_gsm48_tx_mm_serv_rej(). + * Decrement the CM Service use count token and send the message with msc_vlr_tx_cm_serv_rej(). + * + * Until we accept the CM Service Request message as such, there is no use count placed for the service type. + * So in here use msc_gsm48_tx_mm_serv_rej() to respond. + */ /* Make sure that both header and CM Service Request fit into the buffer */ if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*req)) { @@ -703,6 +742,12 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE); } + rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false); + if (rc) { + LOG_MSC_A(msc_a, LOGL_ERROR, "Rx CM SERVICE REQUEST: unable to decode Mobile Identity\n"); + return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE); + } + gh = (struct gsm48_hdr *) msgb_l3(msg); req = (struct gsm48_service_request *) gh->data; @@ -719,18 +764,6 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE); } - /* MI (Mobile Identity) LV follows the Classmark2 */ - mi_buf = cm2_buf + cm2_len; - mi_len = mi_buf[0]; - mi = mi_buf + 1; - - /* Prevent buffer overrun: check the length of MI */ - if (mi_buf + mi_len > msg->tail) { - LOG_MSC_A(msc_a, LOGL_ERROR, "Rx CM SERVICE REQUEST: Mobile Identity " - "length=%u is too big\n", cm2_len); - return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE); - } - if (msc_a_is_establishing_auth_ciph(msc_a)) { LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot accept CM Service Request, conn already busy establishing authenticity\n"); @@ -739,12 +772,11 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) } msc_a->complete_layer3_type = COMPLETE_LAYER3_CM_SERVICE_REQ; - msub_update_id_from_mi(msc_a->c.msub, mi, mi_len); + msub_update_id_from_mi(msc_a->c.msub, &mi); LOG_MSC_A_CAT(msc_a, DMM, LOGL_DEBUG, "Rx CM SERVICE REQUEST cm_service_type=%s\n", osmo_cm_service_type_name(req->cm_service_type)); - mi_type = (mi && mi_len) ? (mi[0] & GSM_MI_TYPE_MASK) : GSM_MI_TYPE_NONE; - switch (mi_type) { + switch (mi.type) { case GSM_MI_TYPE_IMSI: case GSM_MI_TYPE_TMSI: /* continue below */ @@ -758,19 +790,22 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) } /* fall-through for non-emergency setup */ default: - LOG_MSC_A(msc_a, LOGL_ERROR, "MI type is not expected: %s\n", gsm48_mi_type_name(mi_type)); + LOG_MSC_A(msc_a, LOGL_ERROR, "MI type is not expected: %s\n", gsm48_mi_type_name(mi.type)); return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE); } - if (!msc_a_cm_service_type_to_use(req->cm_service_type)) + if (!msc_a_cm_service_type_to_use(msc_a, req->cm_service_type)) return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED); - if (msc_a_is_accepted(msc_a)) - return cm_serv_reuse_conn(msc_a, mi_buf, req->cm_service_type); + /* At this point, the CM Service Request message is being accepted. + * Increment the matching use token, and from here on use msc_vlr_tx_cm_serv_rej() to respond in case of + * failure. */ + msc_a_get(msc_a, msc_a_cm_service_type_to_use(msc_a, req->cm_service_type)); - osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, mi_buf); + if (msc_a_is_accepted(msc_a)) + return cm_serv_reuse_conn(msc_a, &mi, req->cm_service_type); - msc_a_get(msc_a, msc_a_cm_service_type_to_use(req->cm_service_type)); + osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &mi); is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU); vlr_proc_acc_req(msc_a->c.fi, @@ -778,9 +813,10 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) net->vlr, msc_a, VLR_PR_ARQ_T_CM_SERV_REQ, req->cm_service_type, - mi-1, &msc_a->via_cell.lai, + &mi, &msc_a->via_cell.lai, is_utran || net->authentication_required, - is_utran ? net->uea_encryption : net->a5_encryption_mask > 0x01, + msc_a_is_ciphering_to_be_attempted(msc_a), + msc_a_is_ciphering_required(msc_a), req->cipher_key_seq, osmo_gsm48_classmark2_is_r99(cm2, cm2_len), is_utran); @@ -801,21 +837,129 @@ int gsm48_rx_mm_serv_req(struct msc_a *msc_a, struct msgb *msg) /* Receive a CM Re-establish Request */ static int gsm48_rx_cm_reest_req(struct msc_a *msc_a, struct msgb *msg) { - uint8_t mi_type; - char mi_string[GSM48_MI_SIZE]; - struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm_network *net = msc_a_net(msc_a); + struct gsm48_hdr *gh; + struct gsm48_service_request *req; + struct gsm48_classmark2 *cm2; + uint8_t *cm2_buf, cm2_len; + bool is_utran; + struct vlr_subscr *vsub; + struct osmo_mobile_identity mi; + struct msub *prev_msub; + struct msc_a *prev_msc_a; - uint8_t classmark2_len = gh->data[1]; - uint8_t *classmark2 = gh->data+2; - uint8_t mi_len = *(classmark2 + classmark2_len); - uint8_t *mi = (classmark2 + classmark2_len + 1); + int rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false); + if (rc) { + LOGP(DMM, LOGL_ERROR, "CM RE-ESTABLISH REQUEST: cannot decode Mobile Identity\n"); + return -EINVAL; + } + + msc_a->complete_layer3_type = COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ; + msub_update_id_from_mi(msc_a->c.msub, &mi); + + DEBUGP(DMM, "<- CM RE-ESTABLISH REQUEST %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); + + gh = (struct gsm48_hdr *) msgb_l3(msg); + req = (struct gsm48_service_request *) gh->data; + + /* Unfortunately in Phase1 the Classmark2 length is variable, so we cannot + * just use gsm48_service_request struct, and need to parse it manually. */ + cm2_len = gh->data[1]; + cm2_buf = gh->data + 2; + cm2 = (struct gsm48_classmark2 *) cm2_buf; + + /* Prevent buffer overrun: check the length of Classmark2 */ + if (cm2_buf + cm2_len > msg->tail) { + LOG_MSC_A(msc_a, LOGL_ERROR, "Rx CM SERVICE REQUEST: Classmark2 " + "length=%u is too big\n", cm2_len); + return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_INCORRECT_MESSAGE); + } - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); - mi_type = mi[0] & GSM_MI_TYPE_MASK; - DEBUGP(DMM, "<- CM RE-ESTABLISH REQUEST MI(%s)=%s\n", gsm48_mi_type_name(mi_type), mi_string); + /* Look up the other, previously active connection for this subscriber */ + vsub = vlr_subscr_find_by_mi(net->vlr, &mi, __func__); + prev_msub = msub_for_vsub(vsub); + prev_msc_a = msub_msc_a(prev_msub); + if (!vsub || !prev_msub || !prev_msc_a) { + LOG_MSC_A(msc_a, LOGL_ERROR, "CM Re-Establish Request for unknown subscriber: %s\n", + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); + if (vsub) + vlr_subscr_put(vsub, __func__); + return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED); + } + + LOG_MSC_A(msc_a, LOGL_NOTICE, "New conn requesting Re-Establishment\n"); + LOG_MSC_A(prev_msc_a, LOGL_NOTICE, "Old conn matching Re-Establishment request (%s)\n", + osmo_use_count_to_str_c(OTC_SELECT, &prev_msc_a->use_count)); + + if (!prev_msc_a->cc.call_leg || !prev_msc_a->cc.active_trans) { + LOG_MSC_A(msc_a, LOGL_ERROR, "CM Re-Establish Request only supported for voice calls\n"); + if (vsub) + vlr_subscr_put(vsub, __func__); + return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_CALL_CAN_NOT_BE_IDENTIFIED); + } + + msc_a_get(prev_msc_a, __func__); + + /* Move the call_leg and active CC trans over to the new msc_a */ + call_leg_reparent(prev_msc_a->cc.call_leg, + msc_a->c.fi, + MSC_EV_CALL_LEG_TERM, + MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, + MSC_EV_CALL_LEG_RTP_COMPLETE); + msc_a->cc.call_leg = prev_msc_a->cc.call_leg; + prev_msc_a->cc.call_leg = NULL; + + msc_a->cc.active_trans = prev_msc_a->cc.active_trans; + msc_a->cc.active_trans->msc_a = msc_a; + msc_a_get(msc_a, MSC_A_USE_CC); + prev_msc_a->cc.active_trans = NULL; + msc_a_put(prev_msc_a, MSC_A_USE_CC); + + /* Dis-associate the VLR subscriber from the previous msc_a, so that we can start a new Process Access Request + * on the new msc_a. */ + if (vsub->proc_arq_fsm) { + osmo_fsm_inst_term(vsub->proc_arq_fsm, OSMO_FSM_TERM_REGULAR, NULL); + vsub->proc_arq_fsm = NULL; + } + if (prev_msub->vsub) { + vlr_subscr_put(prev_msub->vsub, VSUB_USE_MSUB); + prev_msub->vsub = NULL; + } + + /* Clear the previous conn. + * FIXME: we are clearing the previous conn before having authenticated the new conn. That means anyone can send + * CM Re-Establishing requests with arbitrary mobile identities without having to authenticate, and can freely + * Clear any connections at will. */ + msc_a_release_cn(prev_msc_a); + msc_a_put(prev_msc_a, __func__); + prev_msc_a = NULL; + + /* Kick off Authentication and Ciphering for the new conn. */ + is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU); + vlr_proc_acc_req(msc_a->c.fi, + MSC_A_EV_AUTHENTICATED, MSC_A_EV_CN_CLOSE, NULL, + net->vlr, msc_a, + VLR_PR_ARQ_T_CM_RE_ESTABLISH_REQ, 0, + &mi, &msc_a->via_cell.lai, + is_utran || net->authentication_required, + msc_a_is_ciphering_to_be_attempted(msc_a), + msc_a_is_ciphering_required(msc_a), + req->cipher_key_seq, + osmo_gsm48_classmark2_is_r99(cm2, cm2_len), + is_utran); + vlr_subscr_put(vsub, __func__); - /* we don't support CM call re-establishment */ - return msc_gsm48_tx_mm_serv_rej(msc_a, GSM48_REJECT_SRV_OPT_NOT_SUPPORTED); + /* From vlr_proc_acc_req() we expect an implicit dispatch of PR_ARQ_E_START, and we expect + * msc_vlr_subscr_assoc() to already have been called and completed. Has an error occurred? */ + vsub = msc_a_vsub(msc_a); + if (!vsub) { + LOG_MSC_A(msc_a, LOGL_ERROR, "subscriber not allowed to do a CM Service Request\n"); + return -EIO; + } + + vsub->classmark.classmark2 = *cm2; + vsub->classmark.classmark2_len = cm2_len; + return 0; } static int gsm48_rx_mm_imsi_detach_ind(struct msc_a *msc_a, struct msgb *msg) @@ -824,39 +968,41 @@ static int gsm48_rx_mm_imsi_detach_ind(struct msc_a *msc_a, struct msgb *msg) struct gsm48_hdr *gh = msgb_l3(msg); struct gsm48_imsi_detach_ind *idi = (struct gsm48_imsi_detach_ind *) gh->data; - uint8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK; - char mi_string[GSM48_MI_SIZE]; + struct osmo_mobile_identity mi; struct vlr_subscr *vsub = NULL; - gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len); - DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s\n", - gsm48_mi_type_name(mi_type), mi_string); + int rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false); + if (rc) { + LOGP(DMM, LOGL_ERROR, "IMSI DETACH INDICATION: cannot decode Mobile Identity\n"); + return -EINVAL; + } + + DEBUGP(DMM, "IMSI DETACH INDICATION: %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_DETACH)); - switch (mi_type) { + switch (mi.type) { case GSM_MI_TYPE_TMSI: - vsub = vlr_subscr_find_by_tmsi(net->vlr, - tmsi_from_string(mi_string), __func__); + vsub = vlr_subscr_find_by_tmsi(net->vlr, mi.tmsi, __func__); break; case GSM_MI_TYPE_IMSI: - vsub = vlr_subscr_find_by_imsi(net->vlr, mi_string, __func__); + vsub = vlr_subscr_find_by_imsi(net->vlr, mi.imsi, __func__); break; case GSM_MI_TYPE_IMEI: case GSM_MI_TYPE_IMEISV: /* no sim card... FIXME: what to do ? */ - LOGP(DMM, LOGL_ERROR, "MI(%s)=%s: unimplemented mobile identity type\n", - gsm48_mi_type_name(mi_type), mi_string); + LOGP(DMM, LOGL_ERROR, "%s: unimplemented mobile identity type\n", + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); break; default: - LOGP(DMM, LOGL_ERROR, "MI(%s)=%s: unknown mobile identity type\n", - gsm48_mi_type_name(mi_type), mi_string); + LOGP(DMM, LOGL_ERROR, "%s: unknown mobile identity type\n", + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); break; } if (!vsub) { - LOGP(DMM, LOGL_ERROR, "IMSI DETACH for unknown subscriber MI(%s)=%s\n", - gsm48_mi_type_name(mi_type), mi_string); + LOGP(DMM, LOGL_ERROR, "IMSI DETACH for unknown subscriber %s\n", + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); } else { LOGP(DMM, LOGL_INFO, "IMSI DETACH for %s\n", vlr_subscr_name(vsub)); msub_set_vsub(msc_a->c.msub, vsub); @@ -1125,24 +1271,31 @@ static int gsm48_rx_rr_pag_resp(struct msc_a *msc_a, struct msgb *msg) uint8_t classmark2_len = gh->data[1]; uint8_t *classmark2_buf = gh->data+2; struct gsm48_classmark2 *cm2 = (void*)classmark2_buf; - uint8_t *mi_lv = classmark2_buf + classmark2_len; bool is_utran; struct vlr_subscr *vsub; + struct osmo_mobile_identity mi; + int rc; if (msc_a_is_establishing_auth_ciph(msc_a)) { - LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, + LOG_MSC_A_CAT(msc_a, DRR, LOGL_ERROR, "Ignoring Paging Response, conn already busy establishing authenticity\n"); return 0; } if (msc_a_is_accepted(msc_a)) { - LOG_MSC_A_CAT(msc_a, DMM, LOGL_ERROR, "Ignoring Paging Response, conn already established\n"); + LOG_MSC_A_CAT(msc_a, DRR, LOGL_ERROR, "Ignoring Paging Response, conn already established\n"); return 0; } + rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false); + if (rc) { + LOG_MSC_A_CAT(msc_a, DRR, LOGL_ERROR, "Paging Response: cannot decode Mobile Identity\n"); + return -EINVAL; + } + msc_a->complete_layer3_type = COMPLETE_LAYER3_PAGING_RESP; - msub_update_id_from_mi(msc_a->c.msub, mi_lv + 1, *mi_lv); - LOG_MSC_A_CAT(msc_a, DRR, LOGL_DEBUG, "Rx PAGING RESPONSE\n"); + msub_update_id_from_mi(msc_a->c.msub, &mi); + LOG_MSC_A_CAT(msc_a, DRR, LOGL_DEBUG, "Rx PAGING RESPONSE %s\n", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); msc_a_get(msc_a, MSC_A_USE_PAGING_RESPONSE); @@ -1150,9 +1303,10 @@ static int gsm48_rx_rr_pag_resp(struct msc_a *msc_a, struct msgb *msg) vlr_proc_acc_req(msc_a->c.fi, MSC_A_EV_AUTHENTICATED, MSC_A_EV_CN_CLOSE, NULL, net->vlr, msc_a, - VLR_PR_ARQ_T_PAGING_RESP, 0, mi_lv, &msc_a->via_cell.lai, + VLR_PR_ARQ_T_PAGING_RESP, 0, &mi, &msc_a->via_cell.lai, is_utran || net->authentication_required, - is_utran ? net->uea_encryption : net->a5_encryption_mask > 0x01, + msc_a_is_ciphering_to_be_attempted(msc_a), + msc_a_is_ciphering_required(msc_a), pr->key_seq, osmo_gsm48_classmark2_is_r99(cm2, classmark2_len), is_utran); @@ -1161,8 +1315,13 @@ static int gsm48_rx_rr_pag_resp(struct msc_a *msc_a, struct msgb *msg) * msc_vlr_subscr_assoc() to already have been called and completed. Has an error occurred? */ vsub = msc_a_vsub(msc_a); if (!vsub) { - LOG_MSC_A(msc_a, LOGL_ERROR, "subscriber not allowed to do a Paging Response\n"); - msc_a_put(msc_a, MSC_A_USE_PAGING_RESPONSE); + LOG_MSC_A_CAT(msc_a, DRR, LOGL_ERROR, "subscriber not allowed to do a Paging Response\n"); + + /* Above MSC_A_USE_PAGING_RESPONSE may already have been removed by a forced release, put that use only + * if it still exists. (see msc_a_fsm_releasing_onenter()) */ + if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_PAGING_RESPONSE)) + msc_a_put(msc_a, MSC_A_USE_PAGING_RESPONSE); + return -EIO; } @@ -1177,21 +1336,37 @@ static int gsm48_rx_rr_ciphering_mode_complete(struct msc_a *msc_a, struct msgb struct gsm48_hdr *gh = msgb_l3(msg); unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); struct tlv_parsed tp; - struct tlv_p_entry *mi; + struct tlv_p_entry *mi_tlv; + struct osmo_mobile_identity mi; tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); - mi = TLVP_GET(&tp, GSM48_IE_MOBILE_ID); + mi_tlv = TLVP_GET(&tp, GSM48_IE_MOBILE_ID); - if (!mi) + /* IMEI(SV) is optional for this message */ + if (!mi_tlv) return 0; + if (!mi_tlv->len) + return -EINVAL; + + if (osmo_mobile_identity_decode(&mi, mi_tlv->val, mi_tlv->len, false)) { + LOGP(DMM, LOGL_ERROR, "RR Ciphering Mode Complete contains invalid Mobile Identity %s\n", + osmo_hexdump(mi_tlv->val, mi_tlv->len)); + return -EINVAL; + } + if (mi.type != GSM_MI_TYPE_IMEISV) { + LOGP(DMM, LOGL_ERROR, "RR Ciphering Mode Complete contains " + "unexpected Mobile Identity type %s\n", + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); + return -EINVAL; + } LOG_MSC_A(msc_a, LOGL_DEBUG, "RR Ciphering Mode Complete contains Mobile Identity: %s\n", - osmo_mi_name(mi->val, mi->len)); + osmo_mobile_identity_to_str_c(OTC_SELECT, &mi)); if (!vsub) return 0; - return vlr_subscr_rx_id_resp(vsub, mi->val, mi->len); + return vlr_subscr_rx_id_resp(vsub, &mi); } static int gsm48_rx_rr_app_info(struct msc_a *msc_a, struct msgb *msg) @@ -1205,8 +1380,9 @@ static int gsm48_rx_rr_app_info(struct msc_a *msc_a, struct msgb *msg) apdu_len = gh->data[1]; apdu_data = gh->data+2; - DEBUGP(DRR, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s\n", - apdu_id_flags, apdu_len, osmo_hexdump(apdu_data, apdu_len)); + LOG_MSC_A_CAT(msc_a, DRR, LOGL_DEBUG, "Rx RR APPLICATION INFO " + "(id/flags=0x%02x apdu_len=%u apdu=%s)\n", + apdu_id_flags, apdu_len, osmo_hexdump(apdu_data, apdu_len)); /* we're not using the app info blob anywhere, so ignore. */ #if 0 @@ -1232,6 +1408,9 @@ int gsm0408_rcv_rr(struct msc_a *msc_a, struct msgb *msg) case GSM48_MT_RR_APP_INFO: rc = gsm48_rx_rr_app_info(msc_a, msg); break; + case GSM48_MT_RR_UPLINK_RELEASE: + rc = gsm44068_rcv_rr(msc_a, msg); + break; default: LOG_MSC_A_CAT(msc_a, DRR, LOGL_NOTICE, "MSC: Unimplemented %s GSM 04.08 RR " "message\n", gsm48_rr_msg_name(gh->msg_type)); @@ -1287,6 +1466,10 @@ static int msc_vlr_tx_auth_rej(void *msc_conn_ref) static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type) { struct msc_a *msc_a = msc_conn_ref; + + /* Store requested MI type, so we can check the response */ + msc_a->mm_id_req_type = mi_type; + return mm_tx_identity_req(msc_a, mi_type); } @@ -1314,12 +1497,19 @@ int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref, enum osmo_cm_service_type cm_serv static int msc_vlr_tx_common_id(void *msc_conn_ref) { struct msc_a *msc_a = msc_conn_ref; + struct vlr_subscr *vsub = msc_a_vsub(msc_a); struct ran_msg msg = { .msg_type = RAN_MSG_COMMON_ID, .common_id = { - .imsi = msc_a_vsub(msc_a)->imsi, + .imsi = vsub->imsi, + .last_eutran_plmn_present = vsub->sgs.last_eutran_plmn_present, }, }; + if (vsub->sgs.last_eutran_plmn_present) { + memcpy(&msg.common_id.last_eutran_plmn, &vsub->sgs.last_eutran_plmn, + sizeof(vsub->sgs.last_eutran_plmn)); + } + return msc_a_ran_down(msc_a, MSC_ROLE_I, &msg); } @@ -1333,13 +1523,14 @@ static int msc_vlr_tx_mm_info(void *msc_conn_ref) return gsm48_tx_mm_info(msc_a); } -/* VLR asks us to transmit a CM Service Reject */ +/* VLR asks us to transmit a CM Service Reject. + * Decrement the CM Service type's use token and send the CM Service Reject message. */ static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum osmo_cm_service_type cm_service_type, enum gsm48_reject_value cause) { struct msc_a *msc_a = msc_conn_ref; msc_gsm48_tx_mm_serv_rej(msc_a, cause); - msc_a_put(msc_a, msc_a_cm_service_type_to_use(cm_service_type)); + msc_a_put(msc_a, msc_a_cm_service_type_to_use(msc_a, cm_service_type)); return 0; } @@ -1352,7 +1543,9 @@ static void msc_vlr_subscr_update(struct vlr_subscr *subscr) msub_update_id(msub); } -/* VLR informs us that the subscriber has been associated with a conn */ +/* VLR informs us that the subscriber has been associated with a conn. + * The subscriber has *not* been authenticated yet, so the vsub should be protected from potentially invalid information + * from the msc_a. */ static int msc_vlr_subscr_assoc(void *msc_conn_ref, struct vlr_subscr *vsub) { @@ -1362,6 +1555,10 @@ static int msc_vlr_subscr_assoc(void *msc_conn_ref, if (msub_set_vsub(msub, vsub)) return -EINVAL; + + /* FIXME: would be better to modify vsub->* only after the subscriber is authenticated, in + * evaluate_acceptance_outcome(conn_accepted == true). */ + vsub->cs.attached_via_ran = msc_a->c.ran->type; /* In case we have already received Classmark Information before the VLR Subscriber was @@ -1375,6 +1572,24 @@ static int msc_vlr_subscr_assoc(void *msc_conn_ref, return 0; } +static void msc_vlr_subscr_inval(void *msc_conn_ref, struct vlr_subscr *vsub) +{ + /* Search vsub backwards to make sure msc_conn_ref is a valid msc_a instance. */ + struct msub *msub; + OSMO_ASSERT(vsub); + llist_for_each_entry(msub, &msub_list, entry) { + struct msc_a *msc_a; + if (msub->vsub != vsub) + continue; + + msc_a = msub_msc_a(msub); + if (msc_a) + msc_a_release_cn(msc_a); + + msub->vsub = NULL; + } +} + /* operations that we need to implement for libvlr */ const struct vlr_ops msc_vlr_ops = { .tx_auth_req = msc_vlr_tx_auth_req, @@ -1389,6 +1604,7 @@ const struct vlr_ops msc_vlr_ops = { .tx_mm_info = msc_vlr_tx_mm_info, .subscr_update = msc_vlr_subscr_update, .subscr_assoc = msc_vlr_subscr_assoc, + .subscr_inval = msc_vlr_subscr_inval, }; struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value) @@ -1423,27 +1639,3 @@ struct msgb *gsm48_create_loc_upd_rej(uint8_t cause) gh->data[0] = cause; return msg; } - -int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type) -{ - /* Check the size for the classmark */ - if (length < 1 + *classmark2_lv) - return -1; - - uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1; - if (length < 2 + *classmark2_lv + mi_lv[0]) - return -2; - - *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; - return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv); -} - -int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length, - char *mi_string, uint8_t *mi_type) -{ - static const uint32_t classmark_offset = - offsetof(struct gsm48_pag_resp, classmark2); - uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2; - return gsm48_extract_mi(classmark2_lv, length - classmark_offset, - mi_string, mi_type); -} diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c index ed74e88bb..63b1699ab 100644 --- a/src/libmsc/gsm_04_08_cc.c +++ b/src/libmsc/gsm_04_08_cc.c @@ -43,6 +43,7 @@ #include <osmocom/msc/gsm_09_11.h> #include <osmocom/msc/signal.h> #include <osmocom/msc/transaction.h> +#include <osmocom/msc/transaction_cc.h> #include <osmocom/msc/silent_call.h> #include <osmocom/msc/mncc_int.h> #include <osmocom/abis/e1_input.h> @@ -55,6 +56,8 @@ #include <osmocom/msc/rtp_stream.h> #include <osmocom/msc/mncc_call.h> #include <osmocom/msc/msc_t.h> +#include <osmocom/msc/sdp_msg.h> +#include <osmocom/msc/codec_mapping.h> #include <osmocom/gsm/gsm48.h> #include <osmocom/gsm/gsm0480.h> @@ -126,7 +129,7 @@ static void gsm48_start_guard_timer(struct gsm_trans *trans) /* Call Control */ -void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg) +static void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg) { net->mncc_recv(net, msg); } @@ -163,20 +166,22 @@ static void count_statistics(struct gsm_trans *trans, int new_state) /* state incoming */ switch (new_state) { case GSM_CSTATE_ACTIVE: - osmo_stat_item_inc(trans->net->statg->items[MSC_STAT_ACTIVE_CALLS], 1); - rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_ACTIVE]); + osmo_stat_item_inc(osmo_stat_item_group_get_item(trans->net->statg, MSC_STAT_ACTIVE_CALLS), + 1); + rate_ctr_inc(rate_ctr_group_get_ctr(msc, MSC_CTR_CALL_ACTIVE)); break; } /* state outgoing */ switch (old_state) { case GSM_CSTATE_ACTIVE: - osmo_stat_item_dec(trans->net->statg->items[MSC_STAT_ACTIVE_CALLS], 1); + osmo_stat_item_dec(osmo_stat_item_group_get_item(trans->net->statg, MSC_STAT_ACTIVE_CALLS), + 1); if (new_state == GSM_CSTATE_DISCONNECT_REQ || new_state == GSM_CSTATE_DISCONNECT_IND) - rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_COMPLETE]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc, MSC_CTR_CALL_COMPLETE)); else - rate_ctr_inc(&msc->ctr[MSC_CTR_CALL_INCOMPLETE]); + rate_ctr_inc(rate_ctr_group_get_ctr(msc, MSC_CTR_CALL_INCOMPLETE)); break; } } @@ -226,15 +231,82 @@ static void gsm48_stop_cc_timer(struct gsm_trans *trans) } } -static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, - int msg_type, struct gsm_mncc *mncc) +/* Log the MNCC tx and rx events. + * Depending on msg_type, also log whether RTP information is passed on. + * (This is particularly interesting for the doc/sequence_charts/msc_log_to_ladder.py) + */ +#define log_mncc_rx_tx(ARGS...) _log_mncc_rx_tx(__FILE__, __LINE__, ##ARGS) +static void _log_mncc_rx_tx(const char *file, int line, + struct gsm_trans *trans, const char *rx_tx, const union mncc_msg *mncc) +{ + const char *sdp = NULL; + struct sdp_msg sdp_msg = {}; + struct osmo_sockaddr addr = {}; + + if (!log_check_level(DMNCC, LOGL_DEBUG)) + return; + + switch (mncc->msg_type) { + case MNCC_RTP_CREATE: + case MNCC_RTP_CONNECT: + addr = (struct osmo_sockaddr){ .u.sas = mncc->rtp.addr }; + sdp = mncc->rtp.sdp; + break; + + case MNCC_SETUP_IND: + case MNCC_SETUP_REQ: + case MNCC_SETUP_COMPL_IND: + case MNCC_SETUP_COMPL_REQ: + case MNCC_SETUP_RSP: + case MNCC_SETUP_CNF: + case MNCC_CALL_CONF_IND: + case MNCC_CALL_PROC_REQ: + case MNCC_ALERT_IND: + case MNCC_ALERT_REQ: + sdp = mncc->signal.sdp; + break; + + default: + break; + } + + if (sdp && sdp[0]) { + int rc = sdp_msg_from_sdp_str(&sdp_msg, sdp); + if (rc != 0) { + LOG_TRANS_CAT_SRC(trans, DMNCC, LOGL_ERROR, file, line, "%s %s: invalid SDP message (trying anyway)\n", + rx_tx, + get_mncc_name(mncc->msg_type)); + LOG_TRANS_CAT_SRC(trans, DMNCC, LOGL_DEBUG, file, line, "erratic SDP: %s\n", + osmo_quote_cstr_c(OTC_SELECT, sdp, -1)); + return; + } + LOG_TRANS_CAT_SRC(trans, DMNCC, LOGL_DEBUG, file, line, "%s %s (RTP=%s)\n", + rx_tx, + get_mncc_name(mncc->msg_type), + sdp_msg_to_str(&sdp_msg)); + return; + } + + if (osmo_sockaddr_is_any(&addr) == 0) { + LOG_TRANS_CAT_SRC(trans, DMNCC, LOGL_DEBUG, file, line, "%s %s (RTP=%s)\n", + rx_tx, + get_mncc_name(mncc->msg_type), + osmo_sockaddr_to_str_c(OTC_SELECT, &addr)); + return; + } + + LOG_TRANS_CAT_SRC(trans, DMNCC, LOGL_DEBUG, file, line, "%s %s\n", rx_tx, get_mncc_name(mncc->msg_type)); +} + +#define mncc_recvmsg(ARGS...) _mncc_recvmsg(__FILE__, __LINE__, ##ARGS) +static int _mncc_recvmsg(const char *file, int line, + struct gsm_network *net, struct gsm_trans *trans, int msg_type, struct gsm_mncc *mncc) { struct msgb *msg; unsigned char *data; - LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "tx %s\n", get_mncc_name(msg_type)); - mncc->msg_type = msg_type; + log_mncc_rx_tx(trans, "tx", (union mncc_msg *)mncc); msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC"); if (!msg) @@ -244,6 +316,9 @@ static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans, memcpy(data, mncc, sizeof(struct gsm_mncc)); cc_tx_to_mncc(net, msg); + /* trans may be NULL when sending an MNCC error reply upon an invalid MNCC request */ + if (trans) + trans->cc.mncc_initiated = true; return 0; } @@ -269,6 +344,15 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans) /* send release to L4, if callref still exists */ if (trans->callref) { + /* Send MNCC REL.ind (cause='Resource unavailable') */ + if (trans->cc.mncc_initiated) { + mncc_release_ind(trans->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + (trans->cc.state == GSM_CSTATE_CALL_RECEIVED) ? + GSM48_CC_CAUSE_USER_NOTRESPOND : + GSM48_CC_CAUSE_RESOURCE_UNAVAIL); + } + /* FIXME: currently, a CC trans that would not yet be in state GSM_CSTATE_RELEASE_REQ fails to send a * CC Release to the MS if it gets freed here. Hack it to do so. */ if (trans->cc.state != GSM_CSTATE_RELEASE_REQ) { @@ -277,10 +361,6 @@ void _gsm48_cc_trans_free(struct gsm_trans *trans) mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, GSM48_CC_CAUSE_RESOURCE_UNAVAIL); gsm48_cc_tx_release(trans, &rel); } - /* Ressource unavailable */ - mncc_release_ind(trans->net, trans, trans->callref, - GSM48_CAUSE_LOC_PRN_S_LU, - GSM48_CC_CAUSE_RESOURCE_UNAVAIL); /* This is a final freeing of the transaction. The MNCC release may have triggered the * T308 release timer, but we don't have the luxury of graceful CC Release here. */ gsm48_stop_cc_timer(trans); @@ -310,6 +390,20 @@ static void cc_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans) msc_a_get(msc_a, MSC_A_USE_CC); trans->msc_a = msc_a; trans->paging_request = NULL; + + /* Get the GCR from the MO call leg (if any). */ + if (!trans->cc.lcls) + trans->cc.lcls = trans_lcls_compose(trans, true); + if (trans->cc.lcls && trans->cc.msg.fields & MNCC_F_GCR) { + int rc = osmo_dec_gcr(&trans->cc.lcls->gcr, + &trans->cc.msg.gcr[0], + sizeof(trans->cc.msg.gcr)); + if (rc < 0) + LOG_TRANS(trans, LOGL_ERROR, "Failed to parse GCR\n"); + else + trans->cc.lcls->gcr_available = true; + } + osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans); /* send SETUP request to called party */ gsm48_cc_tx_setup(trans, &trans->cc.msg); @@ -329,8 +423,8 @@ static void cc_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans) /* bridge channels of two transactions */ static int tch_bridge(struct gsm_network *net, const struct gsm_mncc_bridge *bridge) { - struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[0]); - struct gsm_trans *trans2 = trans_find_by_callref(net, bridge->callref[1]); + struct gsm_trans *trans1 = trans_find_by_callref(net, TRANS_CC, bridge->callref[0]); + struct gsm_trans *trans2 = trans_find_by_callref(net, TRANS_CC, bridge->callref[1]); struct call_leg *cl1; struct call_leg *cl2; @@ -355,7 +449,7 @@ static int tch_bridge(struct gsm_network *net, const struct gsm_mncc_bridge *bri cl1 = trans1->msc_a->cc.call_leg; cl2 = trans2->msc_a->cc.call_leg; - return call_leg_local_bridge(cl1, trans1->callref, trans1, cl2, trans2->callref, trans2); + return call_leg_local_bridge(cl1, trans1->call_id, trans1, cl2, trans2->call_id, trans2); } static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) @@ -374,6 +468,8 @@ static void gsm48_cc_timeout(void *arg) int l4_location = GSM48_CAUSE_LOC_PRN_S_LU; struct gsm_mncc mo_rel, l4_rel; + LOG_TRANS(trans, LOGL_INFO, "Timeout of T%x\n", trans->cc.Tcurrent); + memset(&mo_rel, 0, sizeof(struct gsm_mncc)); mo_rel.callref = trans->callref; memset(&l4_rel, 0, sizeof(struct gsm_mncc)); @@ -450,8 +546,8 @@ static void gsm48_cc_timeout(void *arg) static inline void disconnect_bridge(struct gsm_network *net, const struct gsm_mncc_bridge *bridge, int err) { - struct gsm_trans *trans0 = trans_find_by_callref(net, bridge->callref[0]); - struct gsm_trans *trans1 = trans_find_by_callref(net, bridge->callref[1]); + struct gsm_trans *trans0 = trans_find_by_callref(net, TRANS_CC, bridge->callref[0]); + struct gsm_trans *trans1 = trans_find_by_callref(net, TRANS_CC, bridge->callref[1]); struct gsm_mncc mx_rel; if (!trans0 || !trans1) return; @@ -494,6 +590,26 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) memset(&setup, 0, sizeof(struct gsm_mncc)); setup.callref = trans->callref; + /* New Global Call Reference */ + if (!trans->cc.lcls) + trans->cc.lcls = trans_lcls_compose(trans, true); + + /* Pass the LCLS GCR on to the MT call leg via MNCC */ + if (trans->cc.lcls) { + struct msgb *gcr_msg = msgb_alloc(sizeof(setup.gcr), "MNCC GCR"); + const struct osmo_gcr_parsed *gcr = &trans->cc.lcls->gcr; + int rc; + + if (gcr_msg != NULL && (rc = osmo_enc_gcr(gcr_msg, gcr)) > 0) { + memcpy(&setup.gcr[0], gcr_msg->data, rc); + setup.fields |= MNCC_F_GCR; + } else + LOG_TRANS(trans, LOGL_ERROR, "Failed to encode GCR\n"); + msgb_free(gcr_msg); + } + + OSMO_ASSERT(trans->msc_a); + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); /* emergency setup is identified by msg_type */ if (msg_type == GSM48_MT_CC_EMERG_SETUP) { @@ -538,6 +654,20 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) gsm48_decode_called(&setup.called, TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1); } + /* low layer compatibility */ + if (TLVP_PRESENT(&tp, GSM48_IE_LOWL_COMPAT) && TLVP_LEN(&tp, GSM48_IE_LOWL_COMPAT) > 0 && + TLVP_LEN(&tp, GSM48_IE_LOWL_COMPAT) <= sizeof(setup.llc.compat)) { + setup.fields |= MNCC_F_LOWL_COMPAT; + setup.llc.len = TLVP_LEN(&tp, GSM48_IE_LOWL_COMPAT); + memcpy(setup.llc.compat, TLVP_VAL(&tp, GSM48_IE_LOWL_COMPAT), setup.llc.len); + } + /* high layer compatibility */ + if (TLVP_PRESENT(&tp, GSM48_IE_HIGHL_COMPAT) && TLVP_LEN(&tp, GSM48_IE_HIGHL_COMPAT) > 0 && + TLVP_LEN(&tp, GSM48_IE_HIGHL_COMPAT) <= sizeof(setup.hlc.compat)) { + setup.fields |= MNCC_F_HIGHL_COMPAT; + setup.hlc.len = TLVP_LEN(&tp, GSM48_IE_HIGHL_COMPAT); + memcpy(setup.hlc.compat, TLVP_VAL(&tp, GSM48_IE_HIGHL_COMPAT), setup.hlc.len); + } /* user-user */ if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) { setup.fields |= MNCC_F_USERUSER; @@ -563,28 +693,145 @@ static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg) TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1); } - new_cc_state(trans, GSM_CSTATE_INITIATED); + /* MO call leg starting, gather all codec information so far known: */ + trans_cc_filter_init(trans); + trans_cc_filter_set_ran(trans, trans->msc_a->c.ran->type); + trans_cc_filter_set_bss(trans, trans->msc_a); + if (setup.fields & MNCC_F_BEARER_CAP) + trans_cc_filter_set_ms_from_bc(trans, &trans->bearer_cap); + trans_cc_filter_run(trans); LOG_TRANS(trans, setup.emergency ? LOGL_NOTICE : LOGL_INFO, "%sSETUP to %s\n", setup.emergency ? "EMERGENCY_" : "", setup.called.number); - rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]); + rate_ctr_inc(rate_ctr_group_get_ctr(trans->net->msc_ctrs, MSC_CTR_CALL_MO_SETUP)); + + new_cc_state(trans, GSM_CSTATE_INITIATED); + + /* To complete the MNCC_SETUP_IND, we need to provide an RTP address and port. First instruct the MGW to create + * a CN-side RTP conn, and continue with MNCC_SETUP_IND once that is done. Leave trans.cc in GSM_CSTATE_NULL and + * note down the msg_type to indicate that we indeed composed an MNCC_SETUP_IND for later. */ + setup.msg_type = MNCC_SETUP_IND; + trans->cc.msg = setup; + return msc_a_try_call_assignment(trans); + /* continue in gsm48_cc_rx_setup_cn_local_rtp_port_known() */ +} + +/* Callback for MNCC_SETUP_IND waiting for the core network RTP port to be established by the MGW (via msc_a) */ +void gsm48_cc_rx_setup_cn_local_rtp_port_known(struct gsm_trans *trans) +{ + struct msc_a *msc_a = trans->msc_a; + struct gsm_mncc setup = trans->cc.msg; + struct osmo_sockaddr_str *rtp_cn_local; + struct sdp_msg *sdp; + int rc; + + if (trans->cc.state != GSM_CSTATE_INITIATED + || setup.msg_type != MNCC_SETUP_IND) { + LOG_TRANS(trans, LOGL_ERROR, + "Unexpected CC state. Expected GSM_CSTATE_INITIATED and a buffered MNCC_SETUP_IND message," + " found CC state %d and msg_type %s\n", + trans->cc.state, get_mncc_name(setup.msg_type)); + trans->callref = 0; + trans_free(trans); + return; + } + + if (!msc_a) { + LOG_TRANS(trans, LOGL_ERROR, "No connection for CC trans\n"); + trans->callref = 0; + trans_free(trans); + return; + } + + /* 'setup' above has taken the value of trans->cc.msg, we can now clear that. */ + trans->cc.msg = (struct gsm_mncc){}; + + /* Insert the CN side RTP port now available into SDP and compose SDP string */ + rtp_cn_local = call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_CN); + if (!osmo_sockaddr_str_is_nonzero(rtp_cn_local)) { + LOG_TRANS(trans, LOGL_ERROR, "Cannot compose SDP for MNCC_SETUP_IND: no RTP set up for the CN side\n"); + trans_free(trans); + return; + } + trans->cc.local.rtp = *rtp_cn_local; + + sdp = trans->cc.local.audio_codecs.count ? &trans->cc.local : NULL; + rc = sdp_msg_to_sdp_str_buf(setup.sdp, sizeof(setup.sdp), sdp); + if (rc >= sizeof(setup.sdp)) { + LOG_TRANS(trans, LOGL_ERROR, "MNCC_SETUP_IND: SDP too long (%d > %zu bytes)\n", rc, sizeof(setup.sdp)); + trans_free(trans); + return; + } /* indicate setup to MNCC */ mncc_recvmsg(trans->net, trans, MNCC_SETUP_IND, &setup); +} - /* MNCC code will modify the channel asynchronously, we should - * ipaccess-bind only after the modification has been made to the - * lchan->tch_mode */ - return 0; +static void rx_mncc_sdp(struct gsm_trans *trans, uint32_t mncc_msg_type, const char *sdp, + const struct gsm_mncc_bearer_cap *bcap) +{ + struct codec_filter *codecs = &trans->cc.codecs; + struct call_leg *cl = trans->msc_a ? trans->msc_a->cc.call_leg : NULL; + struct rtp_stream *rtp_cn = cl ? cl->rtp[RTP_TO_CN] : NULL; + + if (sdp[0]) { + int rc = sdp_msg_from_sdp_str(&trans->cc.remote, sdp); + if (rc) + LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "rx %s: Failed to parse SDP: %d. Trying anyway.\n", + get_mncc_name(mncc_msg_type), rc); + } + + /* if there is no SDP information or we failed to parse it, try using the Bearer Cap from MNCC, if any. */ + if (!trans->cc.remote.audio_codecs.count && bcap) { + trans->cc.remote = (struct sdp_msg){}; + trans_cc_set_remote_from_bc(trans, bcap); + LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s Bearer Cap: remote=%s\n", + get_mncc_name(mncc_msg_type), sdp_msg_to_str(&trans->cc.remote)); + } + + if (!trans->cc.remote.audio_codecs.count) + LOG_TRANS(trans, LOGL_INFO, + "Got no information of remote audio codecs: neither SDP nor Bearer Capability. Trying anyway.\n"); + + trans_cc_filter_run(trans); + if (rtp_cn) { + rtp_stream_set_remote_addr_and_codecs(rtp_cn, &trans->cc.remote); + rtp_stream_commit(rtp_cn); + } + + /* See if we need to switch codecs to maintain TFO: has the remote side changed the codecs information? If we + * have already assigned a specific codec here, but the remote call leg has now chosen a different codec, we + * need to re-assign this call leg to match the remote leg. */ + if (!sdp_audio_codec_is_set(&codecs->assignment)) { + /* Voice channel assignment has not completed. Do not interfere. */ + return; + } + if (!trans->cc.remote.audio_codecs.count) { + /* Don't know remote codecs, nothing to do. */ + return; + } + if (sdp_audio_codecs_by_descr(&trans->cc.remote.audio_codecs, &codecs->assignment)) { + /* The assigned codec is part of the remote codec set. All is well. */ + /* TODO: maybe this should require exactly the *first* remote codec to match, because we cannot flexibly + * transcode, and assume the actual payload we will receive is listed in the first place? */ + return; + } + + /* We've already completed Assignment of a voice channel (some time ago), and now the remote side has changed + * to a mismatching codec (list). Try to re-assign this side to a matching codec. */ + LOG_TRANS(trans, LOGL_INFO, "Remote call leg mismatches assigned codec: %s\n", + codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote)); + msc_a_tx_assignment_cmd(trans->msc_a); } static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg) { - struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC STUP"); + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC SETUP"); struct gsm48_hdr *gh; struct gsm_mncc *setup = arg; int rc, trans_id; + struct gsm_mncc_bearer_cap bearer_cap; gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); @@ -620,13 +867,102 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg) gsm48_start_cc_timer(trans, 0x303, GSM48_T303); - /* bearer capability */ - if (setup->fields & MNCC_F_BEARER_CAP) { - /* Create a copy of the bearer capability in the transaction struct, so we - * can use this information later */ - memcpy(&trans->bearer_cap, &setup->bearer_cap, sizeof(trans->bearer_cap)); - gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap); + /* MT call leg is starting. Gather all codecs information so far known. + * (Usually) paging has succeeded, and now we're processing the MNCC Setup from the remote MO call leg. + * Initialize the codecs filter with this side's BSS' codec list, received at Complete Layer 3. + * We haven't received the MT MS's Bearer Capabilities yet; the Bearer Capabilities handled here are + * actually the remote call leg's Bearer Capabilities. */ + trans_cc_filter_init(trans); + trans_cc_filter_set_ran(trans, trans->msc_a->c.ran->type); + trans_cc_filter_set_bss(trans, trans->msc_a); + if (setup->fields & MNCC_F_BEARER_CAP) + trans->bearer_cap.transfer = setup->bearer_cap.transfer; + + switch (trans->bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + /* if SDP is included in the MNCC, take that as definitive list of remote audio codecs. */ + rx_mncc_sdp(trans, setup->msg_type, setup->sdp, + (setup->fields & MNCC_F_BEARER_CAP) ? &setup->bearer_cap : NULL); + /* rx_mncc_sdp() has called trans_cc_filter_run(trans); */ + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (setup->fields & MNCC_F_BEARER_CAP) { + trans->cc.remote = (struct sdp_msg){}; + trans_cc_set_remote_from_bc(trans, &setup->bearer_cap); + LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s Bearer Cap: remote=%s\n", + get_mncc_name(setup->msg_type), sdp_msg_to_str(&trans->cc.remote)); + } else { + LOG_TRANS(trans, LOGL_INFO, + "Got no information of remote Bearer Capability. Trying anyway.\n"); + sdp_audio_codecs_set_csd(&trans->cc.codecs.ms); + } + trans_cc_filter_run(trans); + break; + default: + LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n", + trans->bearer_cap.transfer); + break; } + + /* Compose Bearer Capability information that reflects only the codecs (Speech Versions) / CSD bearer services + * remaining after intersecting MS, BSS and remote call leg restrictions. To store in trans for later use, and + * to include in the outgoing CC Setup message. */ + switch (trans->bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + bearer_cap = (struct gsm_mncc_bearer_cap){ + .speech_ver = { -1 }, + }; + sdp_audio_codecs_to_bearer_cap(&bearer_cap, &trans->cc.local.audio_codecs); + rc = bearer_cap_set_radio(&bearer_cap); + if (rc) { + LOG_TRANS(trans, LOGL_ERROR, "Error composing Bearer Capability for CC Setup\n"); + trans_free(trans); + msgb_free(msg); + return rc; + } + /* If no resulting codecs remain, error out. We cannot find a codec that matches both call legs. If the MGW were + * able to transcode, we could use non-identical codecs on each conn of the MGW endpoint, but we are aiming for + * finding a matching codec. */ + if (bearer_cap.speech_ver[0] == -1) { + LOG_TRANS(trans, LOGL_ERROR, "%s: no codec match possible: %s\n", + get_mncc_name(setup->msg_type), + codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote)); + + /* incompatible codecs */ + rc = mncc_release_ind(trans->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INCOMPAT_DEST /* TODO: correct cause code? */); + trans->callref = 0; + trans_free(trans); + msgb_free(msg); + return rc; + } + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (csd_bs_list_to_bearer_cap(&bearer_cap, &trans->cc.local.bearer_services) == 0) { + LOG_TRANS(trans, LOGL_ERROR, "Error composing Bearer Capability for CC Setup\n"); + + /* incompatible codecs */ + rc = mncc_release_ind(trans->net, trans, trans->callref, + GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INCOMPAT_DEST /* TODO: correct cause code? */); + trans->callref = 0; + trans_free(trans); + msgb_free(msg); + return rc; + } + break; + } + + /* Create a copy of the bearer capability in the transaction struct, so we can use this information later */ + trans->bearer_cap = bearer_cap; + + gsm48_encode_bearer_cap(msg, 0, &bearer_cap); + /* facility */ if (setup->fields & MNCC_F_FACILITY) gsm48_encode_facility(msg, 0, &setup->facility); @@ -639,6 +975,12 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg) /* called party BCD number */ if (setup->fields & MNCC_F_CALLED) gsm48_encode_called(msg, &setup->called); + /* low layer compatibility */ + if (setup->fields & MNCC_F_LOWL_COMPAT && setup->llc.len > 0 && setup->llc.len <= sizeof(setup->llc.compat)) + msgb_tlv_put(msg, GSM48_IE_LOWL_COMPAT, setup->llc.len, setup->llc.compat); + /* high layer compatibility */ + if (setup->fields & MNCC_F_HIGHL_COMPAT && setup->hlc.len > 0 && setup->hlc.len <= sizeof(setup->hlc.compat)) + msgb_tlv_put(msg, GSM48_IE_HIGHL_COMPAT, setup->hlc.len, setup->hlc.compat); /* user-user */ if (setup->fields & MNCC_F_USERUSER) gsm48_encode_useruser(msg, 0, &setup->useruser); @@ -651,7 +993,7 @@ static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg) new_cc_state(trans, GSM_CSTATE_CALL_PRESENT); - rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP]); + rate_ctr_inc(rate_ctr_group_get_ctr(trans->net->msc_ctrs, MSC_CTR_CALL_MT_SETUP)); return trans_tx_gsm48(trans, msg); } @@ -687,9 +1029,14 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) /* Create a copy of the bearer capability * in the transaction struct, so we can use * this information later */ - memcpy(&trans->bearer_cap,&call_conf.bearer_cap, + memcpy(&trans->bearer_cap, &call_conf.bearer_cap, sizeof(trans->bearer_cap)); + + /* This is the MT call leg's Call Conf, containing the MS Bearer Capabilities of the MT MS. + * Store in codecs filter. */ + trans_cc_filter_set_ms_from_bc(trans, &call_conf.bearer_cap); } + /* cause */ if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) { call_conf.fields |= MNCC_F_CAUSE; @@ -706,8 +1053,6 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) /* IMSI of called subscriber */ OSMO_STRLCPY_ARRAY(call_conf.imsi, trans->vsub->imsi); - new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); - /* Assign call (if not done yet) */ rc = msc_a_try_call_assignment(trans); @@ -716,8 +1061,47 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) if (rc) return rc; - return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND, - &call_conf); + /* Directly ack with MNCC_CALL_CONF_IND, not yet containing SDP or RTP IP:port information. */ + new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); + return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND, &call_conf); +} + +static int mncc_recv_rtp(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref, + int cmd, struct osmo_sockaddr_str *rtp_addr, uint32_t payload_type, + uint32_t payload_msg_type, const struct sdp_msg *sdp); + +static int gsm48_cc_mt_rtp_port_and_codec_known(struct gsm_trans *trans) +{ + struct msc_a *msc_a = trans->msc_a; + struct osmo_sockaddr_str *rtp_cn_local; + struct gsm_mncc_rtp; + + if (!msc_a) { + LOG_TRANS(trans, LOGL_ERROR, "No connection for CC trans\n"); + trans->callref = 0; + trans_free(trans); + return -EINVAL; + } + + /* Insert the CN side RTP port now available into SDP */ + rtp_cn_local = call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_CN); + if (!rtp_cn_local) { + LOG_TRANS(trans, LOGL_ERROR, "Cannot compose SDP for MNCC_RTP_CREATE: no RTP set up for the CN side\n"); + trans_free(trans); + return -EINVAL; + } + trans->cc.local.rtp = *rtp_cn_local; + + trans_cc_filter_run(trans); + + /* If we haven't completed Assignment yet, don't sent MNCC_RTP_CREATE */ + if (!sdp_audio_codec_is_set(&trans->cc.codecs.assignment)) { + LOG_TRANS(trans, LOGL_DEBUG, "no codec confirmed by Assignment yet\n"); + return 0; + } + + return mncc_recv_rtp(msc_a_net(msc_a), trans, trans->callref, MNCC_RTP_CREATE, rtp_cn_local, 0, 0, + &trans->cc.local); } static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg) @@ -733,6 +1117,14 @@ static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg) /* bearer capability */ if (proceeding->fields & MNCC_F_BEARER_CAP) { + /* MNCC should not switch from e.g. CSD to speech */ + if (proceeding->bearer_cap.transfer != trans->bearer_cap.transfer) { + LOG_TRANS(trans, LOGL_ERROR, "Unexpected Information Transfer Capability %d from MNCC," + " transaction has %d\n", + proceeding->bearer_cap.transfer, + trans->bearer_cap.transfer); + return -EINVAL; + } gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap); memcpy(&trans->bearer_cap, &proceeding->bearer_cap, sizeof(trans->bearer_cap)); } @@ -757,6 +1149,7 @@ static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); struct tlv_parsed tp; struct gsm_mncc alerting; + int rc; gsm48_stop_cc_timer(trans); gsm48_start_cc_timer(trans, 0x301, GSM48_T301); @@ -786,6 +1179,15 @@ static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED); + trans_cc_filter_run(trans); + rc = sdp_msg_to_sdp_str_buf(alerting.sdp, sizeof(alerting.sdp), &trans->cc.local); + if (rc >= sizeof(alerting.sdp)) { + LOG_TRANS(trans, LOGL_ERROR, "MNCC_ALERT_IND: SDP too long (%d > %zu bytes)\n", + rc, sizeof(alerting.sdp)); + trans_free(trans); + return -EINVAL; + } + return mncc_recvmsg(trans->net, trans, MNCC_ALERT_IND, &alerting); } @@ -795,6 +1197,7 @@ static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg) struct gsm_mncc *alerting = arg; struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC ALERT"); struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + int rc; gh->msg_type = GSM48_MT_CC_ALERTING; @@ -810,7 +1213,13 @@ static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg) new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED); - return trans_tx_gsm48(trans, msg); + if (alerting->sdp[0]) + rx_mncc_sdp(trans, alerting->msg_type, alerting->sdp, + (alerting->fields & MNCC_F_BEARER_CAP) ? &alerting->bearer_cap : NULL); + + /* handle the MNCC event */ + rc = trans_tx_gsm48(trans, msg); + return rc; } static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg) @@ -856,6 +1265,10 @@ static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg) new_cc_state(trans, GSM_CSTATE_CONNECT_IND); + if (connect->sdp[0]) + rx_mncc_sdp(trans, connect->msg_type, connect->sdp, + (connect->fields & MNCC_F_BEARER_CAP) ? &connect->bearer_cap : NULL); + return trans_tx_gsm48(trans, msg); } @@ -896,8 +1309,10 @@ static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg) } new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST); - rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT]); + rate_ctr_inc(rate_ctr_group_get_ctr(trans->net->msc_ctrs, MSC_CTR_CALL_MT_CONNECT)); + trans_cc_filter_run(trans); + sdp_msg_to_sdp_str_buf(connect.sdp, sizeof(connect.sdp), &trans->cc.local); return mncc_recvmsg(trans->net, trans, MNCC_SETUP_CNF, &connect); } @@ -909,7 +1324,7 @@ static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg) gsm48_stop_cc_timer(trans); new_cc_state(trans, GSM_CSTATE_ACTIVE); - rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK]); + rate_ctr_inc(rate_ctr_group_get_ctr(trans->net->msc_ctrs, MSC_CTR_CALL_MO_CONNECT_ACK)); memset(&connect_ack, 0, sizeof(struct gsm_mncc)); connect_ack.callref = trans->callref; @@ -1608,7 +2023,7 @@ static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg) static int mncc_recv_rtp(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref, int cmd, struct osmo_sockaddr_str *rtp_addr, uint32_t payload_type, - uint32_t payload_msg_type) + uint32_t payload_msg_type, const struct sdp_msg *sdp) { uint8_t data[sizeof(struct gsm_mncc)]; struct gsm_mncc_rtp *rtp; @@ -1619,56 +2034,106 @@ static int mncc_recv_rtp(struct gsm_network *net, struct gsm_trans *trans, uint3 rtp->callref = callref; rtp->msg_type = cmd; if (rtp_addr) { - rtp->ip = osmo_htonl(inet_addr(rtp_addr->ip)); - rtp->port = rtp_addr->port; + if (osmo_sockaddr_str_to_sockaddr(rtp_addr, &rtp->addr) < 0) + return -EINVAL; } rtp->payload_type = payload_type; rtp->payload_msg_type = payload_msg_type; + if (sdp) + sdp_msg_to_sdp_str_buf(rtp->sdp, sizeof(rtp->sdp), sdp); return mncc_recvmsg(net, trans, cmd, (struct gsm_mncc *)data); } static void mncc_recv_rtp_err(struct gsm_network *net, struct gsm_trans *trans, uint32_t callref, int cmd) { - mncc_recv_rtp(net, trans, callref, cmd, NULL, 0, 0); + mncc_recv_rtp(net, trans, callref, cmd, NULL, 0, 0, NULL); } -static int tch_rtp_create(struct gsm_network *net, uint32_t callref) +static int tch_rtp_create(struct gsm_network *net, const struct gsm_mncc_rtp *rtp) { struct gsm_trans *trans; /* Find callref */ - trans = trans_find_by_callref(net, callref); + trans = trans_find_by_callref(net, TRANS_CC, rtp->callref); if (!trans) { LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP create for non-existing trans\n"); - mncc_recv_rtp_err(net, trans, callref, MNCC_RTP_CREATE); + mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CREATE); return -EIO; } log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub); if (!trans->msc_a) { LOG_TRANS_CAT(trans, DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n"); - mncc_recv_rtp_err(net, trans, callref, MNCC_RTP_CREATE); + mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CREATE); return 0; } - LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(MNCC_RTP_CREATE)); - - /* When we call msc_mgcp_call_assignment() we will trigger, depending - * on the RAN type the call assignment on the A or Iu interface. - * msc_mgcp_call_assignment() also takes care about sending the CRCX - * command to the MGCP-GW. The CRCX will return the port number, - * where the PBX (e.g. Asterisk) will send its RTP stream to. We - * have to return this port number back to the MNCC by sending - * it back with the TCH_RTP_CREATE message. To make sure that - * this message is sent AFTER the response to CRCX from the - * MGCP-GW has arrived, we need will instruct msc_mgcp_call_assignment() - * to take care of this by setting trans->tch_rtp_create to true. - * This will make sure that gsm48_tch_rtp_create() (below) is - * called as soon as the local port number has become known. */ - trans->tch_rtp_create = true; + log_mncc_rx_tx(trans, "rx", (const union mncc_msg *)rtp); /* Assign call (if not done yet) */ return msc_a_try_call_assignment(trans); } +int cc_on_cn_local_rtp_port_known(struct gsm_trans *cc_trans) +{ + /* Depending on MO or MT call, dispatch the event differently */ + switch (cc_trans->cc.state) { + case GSM_CSTATE_INITIATED: + if (cc_trans->cc.msg.msg_type != MNCC_SETUP_IND) { + LOG_TRANS(cc_trans, LOGL_ERROR, "Assuming MO call, expected MNCC_SETUP_IND to be prepared\n"); + return -EINVAL; + } + /* This is the MO call leg, waiting for a CN RTP be able to send initial MNCC_SETUP_IND. */ + gsm48_cc_rx_setup_cn_local_rtp_port_known(cc_trans); + return 0; + + case GSM_CSTATE_MO_TERM_CALL_CONF: + /* This is the MT call leg, waiting for a CN RTP to be able to send MNCC_CALL_CONF_IND. */ + return gsm48_cc_mt_rtp_port_and_codec_known(cc_trans); + + default: + LOG_TRANS(cc_trans, LOGL_ERROR, "CN RTP address available, but in unexpected state %d\n", + cc_trans->cc.state); + return -EINVAL; + } +} + +int cc_on_assignment_done(struct gsm_trans *trans) +{ + struct msc_a *msc_a = trans->msc_a; + + switch (trans->cc.state) { + case GSM_CSTATE_INITIATED: + case GSM_CSTATE_MO_CALL_PROC: + /* MO call, send ACK in form of an MNCC_RTP_CREATE (below) */ + break; + + case GSM_CSTATE_CALL_RECEIVED: + case GSM_CSTATE_MO_TERM_CALL_CONF: + /* MT call, send ACK in form of an MNCC_RTP_CREATE (below) */ + break; + + case GSM_CSTATE_ACTIVE: + /* already active. We decided to re-assign later on during the call - at time of writing this never + * happens. */ + case GSM_CSTATE_CALL_DELIVERED: + case GSM_CSTATE_CONNECT_IND: + /* MNCC has progressed past the initial assignment. Usually it means that this happened: after + * MNCC_ALERT_REQ, MO has triggered a re-assignment, to adjust MO's codec to MT's codec. */ + LOG_TRANS(trans, LOGL_DEBUG, "Re-Assignment complete\n"); + return 0; + + default: + LOG_TRANS(trans, LOGL_ERROR, "Assignment done in unexpected CC state: %d\n", trans->cc.state); + return -EINVAL; + } + + if (!call_leg_local_ip(msc_a->cc.call_leg, RTP_TO_CN)) { + LOG_TRANS(trans, LOGL_DEBUG, + "Assignment complete, but still waiting for the CRCX OK on the CN side RTP\n"); + return 0; + } + return gsm48_tch_rtp_create(trans); +} + /* Trigger TCH_RTP_CREATE acknowledgement */ int gsm48_tch_rtp_create(struct gsm_trans *trans) { @@ -1680,38 +2145,37 @@ int gsm48_tch_rtp_create(struct gsm_trans *trans) struct call_leg *cl = msc_a->cc.call_leg; struct osmo_sockaddr_str *rtp_cn_local; struct rtp_stream *rtp_cn = cl ? cl->rtp[RTP_TO_CN] : NULL; - uint32_t payload_type; - int payload_msg_type; - const struct mgcp_conn_peer *mgcp_info; + int mncc_payload_msg_type; + struct sdp_audio_codec *codec; + const struct codec_mapping *m; + struct sdp_audio_codecs *codecs; if (!rtp_cn) { LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "Cannot RTP CREATE to MNCC, no RTP set up for the CN side\n"); return -EINVAL; } - if (!rtp_cn->codec_known) { + trans_cc_filter_run(trans); + codecs = &trans->cc.local.audio_codecs; + if (!codecs->count) { LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, - "Cannot RTP CREATE to MNCC, no codec set up for the RTP CN side\n"); + "Cannot RTP CREATE to MNCC, there is no codec available\n"); return -EINVAL; } - /* Codec */ - payload_msg_type = mgcp_codec_to_mncc_payload_msg_type(rtp_cn->codec); - - /* Payload Type number */ - mgcp_info = osmo_mgcpc_ep_ci_get_rtp_info(rtp_cn->ci); - if (mgcp_info && mgcp_info->ptmap_len) - payload_type = map_codec_to_pt(mgcp_info->ptmap, mgcp_info->ptmap_len, rtp_cn->codec); - else - payload_type = rtp_cn->codec; + /* Populate the legacy MNCC codec elements: payload_type and payload_msg_type */ + codec = &codecs->codec[0]; + m = codec_mapping_by_subtype_name(codec->subtype_name); + mncc_payload_msg_type = m ? m->mncc_payload_msg_type : 0; rtp_cn_local = call_leg_local_ip(cl, RTP_TO_CN); if (!rtp_cn_local) { - LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "Cannot RTP CREATE to MNCC, no local RTP IP:port set up\n"); + LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "Cannot RTP CREATE to MNCC, no local RTP IP:port to CN set up\n"); return -EINVAL; } - return mncc_recv_rtp(net, trans, trans->callref, MNCC_RTP_CREATE, rtp_cn_local, payload_type, payload_msg_type); + return mncc_recv_rtp(net, trans, trans->callref, MNCC_RTP_CREATE, rtp_cn_local, + codec->payload_type, mncc_payload_msg_type, &trans->cc.local); } static int tch_rtp_connect(struct gsm_network *net, const struct gsm_mncc_rtp *rtp) @@ -1719,20 +2183,9 @@ static int tch_rtp_connect(struct gsm_network *net, const struct gsm_mncc_rtp *r struct gsm_trans *trans; struct call_leg *cl; struct rtp_stream *rtps; - struct osmo_sockaddr_str rtp_addr; - - /* FIXME: in *rtp we should get the codec information of the remote - * leg. We will have to populate trans->conn->rtp.codec_cn with a - * meaningful value based on this information but unfortunately we - * can't do that yet because the mncc API can not signal dynamic - * payload types yet. This must be fixed first. Also there may be - * additional members necessary in trans->conn->rtp because we - * somehow need to deal with dynamic payload types that do not - * comply to 3gpp's assumptions of payload type numbers on the A - * interface. See also related tickets: OS#3399 and OS1683 */ /* Find callref */ - trans = trans_find_by_callref(net, rtp->callref); + trans = trans_find_by_callref(net, TRANS_CC, rtp->callref); if (!trans) { LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP connect for non-existing trans\n"); mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CONNECT); @@ -1745,22 +2198,23 @@ static int tch_rtp_connect(struct gsm_network *net, const struct gsm_mncc_rtp *r return -EIO; } - LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(MNCC_RTP_CONNECT)); + log_mncc_rx_tx(trans, "rx", (const union mncc_msg *)rtp); + + rx_mncc_sdp(trans, rtp->msg_type, rtp->sdp, NULL); cl = trans->msc_a->cc.call_leg; rtps = cl ? cl->rtp[RTP_TO_CN] : NULL; - - if (!rtps) { - LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP connect for trans without ongoing call\n"); - mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CONNECT); - return -EINVAL; + if (rtps && !osmo_sockaddr_str_is_nonzero(&rtps->remote)) { + /* Didn't get an IP address from SDP. Try legacy MNCC IP address */ + struct osmo_sockaddr_str rtp_addr; + if (osmo_sockaddr_str_from_sockaddr(&rtp_addr, &rtp->addr) < 0) { + LOG_TRANS_CAT(trans, DMNCC, LOGL_ERROR, "RTP connect with invalid IP addr\n"); + mncc_recv_rtp_err(net, trans, rtp->callref, MNCC_RTP_CONNECT); + return -EINVAL; + } + rtp_stream_set_remote_addr(rtps, &rtp_addr); + rtp_stream_commit(rtps); } - - LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(MNCC_RTP_CONNECT)); - - osmo_sockaddr_str_from_32n(&rtp_addr, rtp->ip, rtp->port); - rtp_stream_set_remote_addr(rtps, &rtp_addr); - rtp_stream_commit(rtps); return 0; } @@ -1838,7 +2292,7 @@ static int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg) disconnect_bridge(net, &msg->bridge, -rc); return rc; case MNCC_RTP_CREATE: - return tch_rtp_create(net, msg->rtp.callref); + return tch_rtp_create(net, &msg->rtp); case MNCC_RTP_CONNECT: return tch_rtp_connect(net, &msg->rtp); case MNCC_RTP_FREE: @@ -1859,7 +2313,7 @@ static int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg) data = &msg->signal; /* Find callref */ - trans = trans_find_by_callref(net, data->callref); + trans = trans_find_by_callref(net, TRANS_CC, data->callref); /* Callref unknown */ if (!trans) { @@ -1934,29 +2388,28 @@ static int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg) if (!trans) { LOG_TRANS(trans, LOGL_ERROR, "No memory for trans.\n"); vlr_subscr_put(vsub, __func__); - /* Ressource unavailable */ + /* Resource unavailable */ mncc_release_ind(net, NULL, data->callref, GSM48_CAUSE_LOC_PRN_S_LU, GSM48_CC_CAUSE_RESOURCE_UNAVAIL); return -ENOMEM; } + /* Remember remote SDP, if any */ + rx_mncc_sdp(trans, data->msg_type, data->sdp, + (data->fields & MNCC_F_BEARER_CAP) ? &data->bearer_cap : NULL); + /* If subscriber has no conn */ if (!msc_a) { - - if (vsub->cs.is_paging) { - LOG_TRANS(trans, LOGL_DEBUG, - "rx %s, subscriber not yet connected, paging already started\n", - get_mncc_name(msg->msg_type)); - vlr_subscr_put(vsub, __func__); - trans_free(trans); - return 0; - } + /* This condition will return before the common logging of the received MNCC message below, so + * log it now. */ + log_mncc_rx_tx(trans, "rx", msg); /* store setup information until paging succeeds */ memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc)); - /* Request a channel */ + /* Request a channel. If Paging already started, paging_request_start() will append the new + * trans to the already ongoing Paging. */ trans->paging_request = paging_request_start(vsub, PAGING_CAUSE_CALL_CONVERSATIONAL, cc_paging_cb, trans, "MNCC: establish call"); if (!trans->paging_request) { @@ -1977,9 +2430,30 @@ static int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg) log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub); } - LOG_TRANS_CAT(trans, DMNCC, LOGL_DEBUG, "rx %s\n", get_mncc_name(msg->msg_type)); + log_mncc_rx_tx(trans, "rx", msg); - gsm48_start_guard_timer(trans); + /* + * The step of gsm48_start_guard_timer() needs to be done for + * major state-impacting MNCC messages, but not for those + * that are a mere pass-through to CC messages to MS. + */ + switch (msg->msg_type) { + case MNCC_PROGRESS_REQ: + case MNCC_NOTIFY_REQ: + case MNCC_FACILITY_REQ: + case MNCC_START_DTMF_RSP: + case MNCC_START_DTMF_REJ: + case MNCC_STOP_DTMF_RSP: + case MNCC_HOLD_CNF: + case MNCC_HOLD_REJ: + case MNCC_RETRIEVE_CNF: + case MNCC_RETRIEVE_REJ: + case MNCC_USERINFO_REQ: + break; + default: + gsm48_start_guard_timer(trans); + } + trans->cc.mncc_initiated = true; if (trans->msc_a) msc_a = trans->msc_a; @@ -1989,7 +2463,7 @@ static int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg) struct gsm_mncc rel = { .callref = data->callref, }; - LOG_TRANS(trans, LOGL_DEBUG, "rx %s in paging state\n", get_mncc_name(msg->msg_type)); + LOG_TRANS(trans, LOGL_DEBUG, "still paging\n"); mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU, GSM48_CC_CAUSE_NORM_CALL_CLEAR); if (msg->msg_type == MNCC_REL_REQ) @@ -1999,9 +2473,6 @@ static int mncc_tx_to_gsm_cc(struct gsm_network *net, const union mncc_msg *msg) trans->callref = 0; trans_free(trans); return rc; - } else { - LOG_TRANS(trans, LOGL_DEBUG, "rx %s in state %s\n", - get_mncc_name(msg->msg_type), gsm48_cc_state_name(trans->cc.state)); } /* Find function for current state and message */ diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c index 367cc6faf..aa87a192a 100644 --- a/src/libmsc/gsm_04_11.c +++ b/src/libmsc/gsm_04_11.c @@ -58,7 +58,7 @@ #include <osmocom/msc/paging.h> #ifdef BUILD_SMPP -#include "smpp_smsc.h" +#include <osmocom/smpp/smpp_smsc.h> #endif void *tall_gsms_ctx; @@ -432,7 +432,7 @@ static int sms_route_mt_sms(struct gsm_trans *trans, struct gsm_sms *gsms) LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc); rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; /* rc will be logged by gsm411_send_rp_error() */ - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR)); } return rc; } @@ -442,29 +442,29 @@ try_local: /* determine gsms->receiver based on dialled number */ gsms->receiver = vlr_subscr_find_by_msisdn(net->vlr, gsms->dst.addr, VSUB_USE_SMS_RECEIVER); - if (!gsms->receiver) { + if (gsms->receiver) + return 0; + #ifdef BUILD_SMPP - /* Avoid a second look-up */ - if (smpp_route_smpp_first()) { - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]); - return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; - } + /* Avoid a second look-up */ + if (smpp_route_smpp_first()) { + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_NO_RECEIVER)); + return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + } - rc = smpp_try_deliver(gsms, msc_a); - if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) { - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]); - } else if (rc < 0) { - LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc); - rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; - /* rc will be logged by gsm411_send_rp_error() */ - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR]); - } + rc = smpp_try_deliver(gsms, msc_a); + if (rc == GSM411_RP_CAUSE_MO_NUM_UNASSIGNED) { + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_NO_RECEIVER)); + } else if (rc < 0) { + LOG_TRANS(trans, LOGL_ERROR, "SMS delivery error: %d\n", rc); + rc = GSM411_RP_CAUSE_MO_TEMP_FAIL; + /* rc will be logged by gsm411_send_rp_error() */ + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_DELIVER_UNKNOWN_ERROR)); + } #else - rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]); + rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_NO_RECEIVER)); #endif - } else - rc = 0; return rc; } @@ -501,7 +501,7 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg, } /* FIXME: should we do this on success, after all checks? */ - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_SUBMITTED)); gsms = sms_alloc(); if (!gsms) @@ -626,9 +626,30 @@ static int gsm340_rx_tpdu(struct gsm_trans *trans, struct msgb *msg, osmo_hexdump(gsms->user_data, gsms->user_data_len)); gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp); + if (gsms->validity_minutes < net->sms_queue_cfg->minimum_validity_mins) { + LOG_TRANS(trans, LOGL_INFO, "Overriding user-provided validity period (%lu) " + "with minimum SMSC validity period (%u) minutes\n", gsms->validity_minutes, + net->sms_queue_cfg->minimum_validity_mins); + gsms->validity_minutes = net->sms_queue_cfg->minimum_validity_mins; + } rc = sms_route_mt_sms(trans, gsms); + /* This SMS got routed through SMPP and we are waiting on the response. */ + if (gsms->smpp.esme) { + rc = -EINPROGRESS; + goto out; + } + + /* This SMS got routed through SMPP, but the configured ESME was + * unavailable at this time. This is an OOO condition. + * Don't store this SMS in the database as we may never be + * able to deliver it. (we would need to process the stored SMS and + * attempt re-submission to the ESME) + */ + if (rc == GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER) + goto out; /* free() the message */ + /* * This SMS got routed through SMPP or no receiver exists. * In any case, we store it in the database for further processing. @@ -717,8 +738,10 @@ static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans, return gsm411_send_rp_ack(trans, rph->msg_ref); else if (rc > 0) return gsm411_send_rp_error(trans, rph->msg_ref, rc); - else - return rc; + else if (rc == -EINPROGRESS) + rc = 0; + + return rc; } /* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */ @@ -887,10 +910,10 @@ static int gsm411_rx_rp_error(struct gsm_trans *trans, * to store this in our database and wait for a SMMA message */ /* FIXME */ send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0); - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_RP_ERR_MEM)); } else { send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0); - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_RP_ERR_OTHER)); } sms_free(sms); @@ -1195,7 +1218,7 @@ int gsm411_send_sms(struct gsm_network *net, /* Store a pointer to abstract SMS representation */ trans->sms.sms = sms; - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_DELIVERED)); db_sms_inc_deliver_attempts(trans->sms.sms); return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, @@ -1207,7 +1230,8 @@ int gsm411_send_sms(struct gsm_network *net, int gsm411_send_rp_data(struct gsm_network *net, struct vlr_subscr *vsub, size_t sm_rp_oa_len, const uint8_t *sm_rp_oa, size_t sm_rp_ud_len, const uint8_t *sm_rp_ud, - bool sm_rp_mmts_ind) + bool sm_rp_mmts_ind, const uint8_t *gsup_source_name, + size_t gsup_source_name_len) { struct gsm_trans *trans; struct msgb *msg; @@ -1222,6 +1246,17 @@ int gsm411_send_rp_data(struct gsm_network *net, struct vlr_subscr *vsub, if (trans->msc_a != NULL) gsm411_handle_mmts_ind(trans); + /* Save GSUP source_name for subsequent response messages */ + if (gsup_source_name && gsup_source_name_len) { + trans->sms.gsup_source_name = talloc_memdup(trans, gsup_source_name, + gsup_source_name_len); + if (!trans->sms.gsup_source_name) { + trans_free(trans); + return -ENOMEM; + } + trans->sms.gsup_source_name_len = gsup_source_name_len; + } + /* Allocate a message buffer for to be encoded SMS */ msg = gsm411_msgb_alloc(); if (!msg) { @@ -1238,7 +1273,7 @@ int gsm411_send_rp_data(struct gsm_network *net, struct vlr_subscr *vsub, /* Encode RP-UD itself (SM TPDU) */ msgb_lv_put(msg, sm_rp_ud_len, sm_rp_ud); - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_SMS_DELIVERED)); return gsm411_rp_sendmsg(&trans->sms.smr_inst, msg, GSM411_MT_RP_DATA_MT, trans->sms.sm_rp_mr, @@ -1366,4 +1401,3 @@ void gsm411_sapi_n_reject(struct msc_a *msc_a) trans_free(trans); } } - diff --git a/src/libmsc/gsm_04_11_gsup.c b/src/libmsc/gsm_04_11_gsup.c index 2abfc9209..1afdfabbc 100644 --- a/src/libmsc/gsm_04_11_gsup.c +++ b/src/libmsc/gsm_04_11_gsup.c @@ -1,5 +1,5 @@ /* - * (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com> + * (C) 2018-2019 by Vadim Yanitskiy <axilirator@gmail.com> * * All Rights Reserved * @@ -33,6 +33,7 @@ #include <osmocom/msc/vlr.h> #include <osmocom/msc/msub.h> #include <osmocom/msc/gsup_client_mux.h> +#include <osmocom/msc/msc_a.h> /* Common helper for preparing to be encoded GSUP message */ static void gsup_sm_msg_init(struct osmo_gsup_message *gsup_msg, @@ -53,7 +54,7 @@ static void gsup_sm_msg_init(struct osmo_gsup_message *gsup_msg, int gsm411_gsup_mo_fwd_sm_req(struct gsm_trans *trans, struct msgb *msg, uint8_t sm_rp_mr, uint8_t *sm_rp_da, uint8_t sm_rp_da_len) { - uint8_t bcd_buf[GSM48_MI_SIZE] = { 0 }; + uint8_t bcd_buf[GSM48_MI_SIZE]; struct osmo_gsup_message gsup_msg; size_t bcd_len; @@ -65,22 +66,28 @@ int gsm411_gsup_mo_fwd_sm_req(struct gsm_trans *trans, struct msgb *msg, /* Assign SM-RP-MR to transaction state */ trans->sms.sm_rp_mr = sm_rp_mr; - /* Encode subscriber's MSISDN */ + /* Encode subscriber's MSISDN as LHV (with room for ToN/NPI header) */ bcd_len = gsm48_encode_bcd_number(bcd_buf, sizeof(bcd_buf), - 0, trans->vsub->msisdn); + 1, trans->vsub->msisdn); if (bcd_len <= 0 || bcd_len > sizeof(bcd_buf)) { LOG_TRANS(trans, LOGL_ERROR, "Failed to encode subscriber's MSISDN\n"); return -EINVAL; } + /* NOTE: assuming default ToN/NPI values as we don't have this info */ + bcd_buf[1] = 0x01 /* NPI: ISDN/Telephony Numbering (ITU-T Rec. E.164 / ITU-T Rec. E.163) */ + | (0x01 << 4) /* ToN: International Number */ + | (0x01 << 7); /* No Extension */ + /* Initialize a new GSUP message */ gsup_sm_msg_init(&gsup_msg, OSMO_GSUP_MSGT_MO_FORWARD_SM_REQUEST, trans->vsub->imsi, &sm_rp_mr); - /* According to 12.2.3, the MSISDN from VLR is inserted here */ + /* According to 12.2.3, the MSISDN from VLR is inserted here. + * NOTE: redundant BCD length octet is not included. */ gsup_msg.sm_rp_oa_type = OSMO_GSUP_SMS_SM_RP_ODA_MSISDN; - gsup_msg.sm_rp_oa_len = bcd_len; - gsup_msg.sm_rp_oa = bcd_buf; + gsup_msg.sm_rp_oa_len = bcd_len - 1; + gsup_msg.sm_rp_oa = bcd_buf + 1; /* SM-RP-DA should (already) contain SMSC address */ gsup_msg.sm_rp_da_type = OSMO_GSUP_SMS_SM_RP_ODA_SMSC_ADDR; @@ -91,6 +98,7 @@ int gsm411_gsup_mo_fwd_sm_req(struct gsm_trans *trans, struct msgb *msg, gsup_msg.sm_rp_ui_len = msgb_l4len(msg); gsup_msg.sm_rp_ui = (uint8_t *) msgb_sms(msg); + gsup_client_mux_tx_set_source(trans->net->gcm, &gsup_msg); return gsup_client_mux_tx(trans->net->gcm, &gsup_msg); } @@ -113,6 +121,7 @@ int gsm411_gsup_mo_ready_for_sm_req(struct gsm_trans *trans, uint8_t sm_rp_mr) /* Indicate SMMA as the Alert Reason */ gsup_msg.sm_alert_rsn = OSMO_GSUP_SMS_SM_ALERT_RSN_MEM_AVAIL; + gsup_client_mux_tx_set_source(trans->net->gcm, &gsup_msg); return gsup_client_mux_tx(trans->net->gcm, &gsup_msg); } @@ -143,14 +152,6 @@ static int gsm411_gsup_mo_handler(struct gsm_network *net, struct vlr_subscr *vs OSMO_ASSERT(0); } - /* Make sure that 'SMS over GSUP' is expected */ - if (!net->sms_over_gsup) { - /* TODO: notify sender about that? */ - LOGP(DLSMS, LOGL_NOTICE, "Unexpected MO SMS over GSUP, " - "ignoring message...\n"); - return -EIO; - } - /* Verify GSUP message */ if (!gsup_msg->sm_rp_mr) goto msg_error; @@ -163,7 +164,8 @@ static int gsm411_gsup_mo_handler(struct gsm_network *net, struct vlr_subscr *vs LOGP(DLSMS, LOGL_NOTICE, "No transaction found for %s, " "ignoring %s-%s message...\n", vlr_subscr_name(vsub), msg_name, msg_is_err ? "Err" : "Res"); - return -EIO; /* TODO: notify sender about that? */ + gsup_client_mux_tx_error_reply(net->gcm, gsup_msg, GMM_CAUSE_NO_PDP_ACTIVATED); + return -EIO; } LOG_TRANS(trans, LOGL_DEBUG, "RX %s-%s\n", msg_name, msg_is_err ? "Err" : "Res"); @@ -180,9 +182,8 @@ static int gsm411_gsup_mo_handler(struct gsm_network *net, struct vlr_subscr *vs return 0; msg_error: - /* TODO: notify sender about that? */ - LOGP(DLSMS, LOGL_NOTICE, "RX malformed %s-%s\n", - msg_name, msg_is_err ? "Err" : "Res"); + LOGP(DLSMS, LOGL_NOTICE, "RX malformed %s-%s\n", msg_name, msg_is_err ? "Err" : "Res"); + gsup_client_mux_tx_error_reply(net->gcm, gsup_msg, GMM_CAUSE_INV_MAND_INFO); return -EINVAL; } @@ -199,6 +200,11 @@ int gsm411_gsup_mt_fwd_sm_res(struct gsm_trans *trans, uint8_t sm_rp_mr) gsup_sm_msg_init(&gsup_msg, OSMO_GSUP_MSGT_MT_FORWARD_SM_RESULT, trans->vsub->imsi, &sm_rp_mr); + /* Ensure routing through OsmoHLR to the MT-sending SMSC */ + gsup_msg.destination_name = trans->sms.gsup_source_name; + gsup_msg.destination_name_len = trans->sms.gsup_source_name_len; + gsup_client_mux_tx_set_source(trans->net->gcm, &gsup_msg); + return gsup_client_mux_tx(trans->net->gcm, &gsup_msg); } @@ -216,6 +222,11 @@ int gsm411_gsup_mt_fwd_sm_err(struct gsm_trans *trans, gsup_sm_msg_init(&gsup_msg, OSMO_GSUP_MSGT_MT_FORWARD_SM_ERROR, trans->vsub->imsi, &sm_rp_mr); + /* Ensure routing through OsmoHLR to the MT-sending SMSC */ + gsup_msg.destination_name = trans->sms.gsup_source_name; + gsup_msg.destination_name_len = trans->sms.gsup_source_name_len; + gsup_client_mux_tx_set_source(trans->net->gcm, &gsup_msg); + /* SM-RP-Cause value */ gsup_msg.sm_rp_cause = &cause; @@ -235,14 +246,6 @@ static int gsm411_gsup_mt_handler(struct gsm_network *net, struct vlr_subscr *vs LOGP(DLSMS, LOGL_DEBUG, "RX MT-forwardSM-Req\n"); - /* Make sure that 'SMS over GSUP' is expected */ - if (!net->sms_over_gsup) { - LOGP(DLSMS, LOGL_NOTICE, "Unexpected MT SMS over GSUP, " - "ignoring message...\n"); - /* TODO: notify sender about that? */ - return -EIO; - } - /** * Verify GSUP message * @@ -268,19 +271,20 @@ static int gsm411_gsup_mt_handler(struct gsm_network *net, struct vlr_subscr *vs rc = gsm411_send_rp_data(net, vsub, gsup_msg->sm_rp_oa_len, gsup_msg->sm_rp_oa, gsup_msg->sm_rp_ui_len, gsup_msg->sm_rp_ui, - sm_rp_mmts_ind); + sm_rp_mmts_ind, gsup_msg->source_name, + gsup_msg->source_name_len); if (rc) { LOGP(DLSMS, LOGL_NOTICE, "Failed to send MT SMS, " "ignoring MT-forwardSM-Req message...\n"); - /* TODO: notify sender about that? */ + gsup_client_mux_tx_error_reply(net->gcm, gsup_msg, GMM_CAUSE_NET_FAIL); return rc; } return 0; msg_error: - /* TODO: notify sender about that? */ LOGP(DLSMS, LOGL_NOTICE, "RX malformed MT-forwardSM-Req\n"); + gsup_client_mux_tx_error_reply(net->gcm, gsup_msg, GMM_CAUSE_INV_MAND_INFO); return -EINVAL; } @@ -290,6 +294,14 @@ int gsm411_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gs struct vlr_subscr *vsub; int rc; + /* Make sure that 'SMS over GSUP' is expected */ + if (!net->sms_over_gsup) { + LOGP(DLSMS, LOGL_NOTICE, "Unexpected MO/MT SMS over GSUP " + "(sms-over-gsup is not enabled), ignoring message...\n"); + gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_GPRS_NOTALLOWED); + return -EIO; + } + vsub = vlr_subscr_find_by_imsi(net->vlr, gsup_msg->imsi, __func__); if (!vsub) { LOGP(DLSMS, LOGL_ERROR, "Rx %s for unknown subscriber, rejecting\n", @@ -317,6 +329,7 @@ int gsm411_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gs default: LOGP(DMM, LOGL_ERROR, "No handler found for %s, dropping message...\n", osmo_gsup_message_type_name(gsup_msg->message_type)); + gsup_client_mux_tx_error_reply(gcm, gsup_msg, GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL); rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; } diff --git a/src/libmsc/gsm_04_14.c b/src/libmsc/gsm_04_14.c index 811655813..03c06fde9 100644 --- a/src/libmsc/gsm_04_14.c +++ b/src/libmsc/gsm_04_14.c @@ -43,7 +43,7 @@ static struct msgb *create_gsm0414_msg(uint8_t msg_type) struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.14"); struct gsm48_hdr *gh; - gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); gh->proto_discr = GSM48_PDISC_TEST; gh->msg_type = msg_type; return msg; diff --git a/src/libmsc/gsm_09_11.c b/src/libmsc/gsm_09_11.c index 8a13cdad6..e29389015 100644 --- a/src/libmsc/gsm_09_11.c +++ b/src/libmsc/gsm_09_11.c @@ -125,7 +125,7 @@ int gsm0911_rcv_nc_ss(struct msc_a *msc_a, struct msgb *msg) trans = trans_find_by_id(msc_a, TRANS_USSD, tid); if (!trans) { /* Count MS-initiated attempts to establish a NC SS/USSD session */ - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_REQUESTS]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MO_REQUESTS)); /** * According to GSM TS 04.80, section 2.4.2 "Register @@ -159,7 +159,7 @@ int gsm0911_rcv_nc_ss(struct msc_a *msc_a, struct msgb *msg) ncss_session_timeout_handler, trans); /* Count active NC SS/USSD sessions */ - osmo_stat_item_inc(net->statg->items[MSC_STAT_ACTIVE_NC_SS], 1); + osmo_stat_item_inc(osmo_stat_item_group_get_item(net->statg, MSC_STAT_ACTIVE_NC_SS), 1); trans->dlci = OMSC_LINKID_CB(msg); trans->msc_a = msc_a; @@ -241,7 +241,7 @@ int gsm0911_rcv_nc_ss(struct msc_a *msc_a, struct msgb *msg) /* Count established MS-initiated NC SS/USSD sessions */ if (msg_type == GSM0480_MTYPE_REGISTER) - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_ESTABLISHED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MO_ESTABLISHED)); return rc; @@ -265,7 +265,7 @@ static void ss_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans) if (trans->msc_a) { LOG_MSC_A_CAT(msc_a, DPAG, LOGL_ERROR, - "Handle paging error: transaction already associated with subsciber," + "Handle paging error: transaction already associated with subscriber," " apparently it was already handled. Skip.\n"); return; } @@ -297,7 +297,7 @@ static void ss_paging_cb(struct msc_a *msc_a, struct gsm_trans *trans) trans->ss.msg = NULL; /* Count established network-initiated NC SS/USSD sessions */ - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MT_ESTABLISHED)); } else { struct osmo_gsup_message gsup_msg; @@ -363,7 +363,7 @@ static struct gsm_trans *establish_nc_ss_trans(struct gsm_network *net, } /* Count active NC SS/USSD sessions */ - osmo_stat_item_inc(net->statg->items[MSC_STAT_ACTIVE_NC_SS], 1); + osmo_stat_item_inc(osmo_stat_item_group_get_item(net->statg, MSC_STAT_ACTIVE_NC_SS), 1); /* Init inactivity timer */ osmo_timer_setup(&trans->ss.timer_guard, @@ -415,7 +415,8 @@ void _gsm911_nc_ss_trans_free(struct gsm_trans *trans) osmo_timer_del(&trans->ss.timer_guard); /* One session less */ - osmo_stat_item_dec(trans->net->statg->items[MSC_STAT_ACTIVE_NC_SS], 1); + osmo_stat_item_dec(osmo_stat_item_group_get_item(trans->net->statg, MSC_STAT_ACTIVE_NC_SS), + 1); } int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_message *gsup_msg) @@ -440,7 +441,7 @@ int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_g log_set_context(LOG_CTX_VLR_SUBSCR, vsub); /* Attempt to find DTAP-transaction */ - trans = trans_find_by_callref(net, gsup_msg->session_id); + trans = trans_find_by_callref(net, TRANS_USSD, gsup_msg->session_id); /* Handle errors */ if (OSMO_GSUP_IS_MSGT_ERROR(gsup_msg->message_type)) { @@ -474,7 +475,7 @@ int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_g if (!trans) { /* Count network-initiated attempts to establish a NC SS/USSD session */ - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_REQUESTS]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MT_REQUESTS)); /* Attempt to establish a new transaction */ trans = establish_nc_ss_trans(net, vsub, gsup_msg); @@ -578,7 +579,7 @@ int gsm0911_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_g /* Count established network-initiated NC SS/USSD sessions */ if (gsup_msg->session_state == OSMO_GSUP_SESSION_STATE_BEGIN) - rate_ctr_inc(&net->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, MSC_CTR_NC_SS_MT_ESTABLISHED)); return 0; } diff --git a/src/libmsc/gsup_client_mux.c b/src/libmsc/gsup_client_mux.c index e425651af..e27b6645d 100644 --- a/src/libmsc/gsup_client_mux.c +++ b/src/libmsc/gsup_client_mux.c @@ -1,25 +1,21 @@ /* Directing individual GSUP messages to their respective handlers. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <errno.h> @@ -140,6 +136,25 @@ int gsup_client_mux_tx(struct gsup_client_mux *gcm, const struct osmo_gsup_messa return osmo_gsup_client_send(gcm->gsup_client, msg); } +/* Set GSUP source_name to our local IPA name */ +void gsup_client_mux_tx_set_source(const struct gsup_client_mux *gcm, + struct osmo_gsup_message *gsup_msg) +{ + const char *local_msc_name; + + if (!gcm) + return; + if (!gcm->gsup_client) + return; + if (!gcm->gsup_client->ipa_dev) + return; + local_msc_name = gcm->gsup_client->ipa_dev->serno; + if (!local_msc_name) + return; + gsup_msg->source_name = (const uint8_t *) local_msc_name; + gsup_msg->source_name_len = strlen(local_msc_name) + 1; +} + /* Transmit GSUP error in response to original message */ void gsup_client_mux_tx_error_reply(struct gsup_client_mux *gcm, const struct osmo_gsup_message *gsup_orig, enum gsm48_gmm_cause cause) @@ -154,12 +169,15 @@ void gsup_client_mux_tx_error_reply(struct gsup_client_mux *gcm, const struct os .cause = cause, .message_type = OSMO_GSUP_TO_MSGT_ERROR(gsup_orig->message_type), .message_class = gsup_orig->message_class, + .destination_name = gsup_orig->source_name, + .destination_name_len = gsup_orig->source_name_len, /* 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); + gsup_client_mux_tx_set_source(gcm, &gsup_reply); /* For SS/USSD, it's important to keep both session state and ID IEs */ if (gsup_orig->session_state != OSMO_GSUP_SESSION_STATE_NONE) { diff --git a/src/libmsc/mncc.c b/src/libmsc/mncc.c index d0b2ff263..026dae025 100644 --- a/src/libmsc/mncc.c +++ b/src/libmsc/mncc.c @@ -110,8 +110,6 @@ void mncc_set_cause(struct gsm_mncc *data, int loc, int val) * MNCC validation code. Move to libosmocore once headers are merged ************************************************************************/ -#define MNCC_F_ALL 0x3fff - static int check_string_terminated(const char *str, unsigned int size) { int i; @@ -157,7 +155,7 @@ static int mncc_prim_check_sign(const struct gsm_mncc *mncc_prim) { int rc; - if (mncc_prim->fields & ~ MNCC_F_ALL) { + if (mncc_prim->fields & ~MNCC_F_ALL) { LOGP(DMNCC, LOGL_ERROR, "Unknown MNCC field mask 0x%x\n", mncc_prim->fields); return -EINVAL; } @@ -235,6 +233,34 @@ static int mncc_prim_check_sign(const struct gsm_mncc *mncc_prim) return 0; } +/* Make sure that the SDP section has a terminating \0. The MNCC message may end after that \0, and if SDP is omitted it + * must contain at least one \0 byte. */ +int mncc_check_sdp_termination(const char *label, const struct gsm_mncc *mncc, unsigned int len, const char *sdp) +{ + size_t sdp_offset; + size_t sdp_data_len; + size_t sdp_str_len; + + OSMO_ASSERT(((char*)mncc) < sdp); + + sdp_offset = sdp - (char*)mncc; + if (len < sdp_offset) + goto too_short; + + sdp_data_len = len - sdp_offset; + if (sdp_data_len < 1) + goto too_short; + + sdp_str_len = strnlen(sdp, sdp_data_len); + /* There must be a \0, so sdp_str_len must be at most sdp_data_len - 1 */ + if (sdp_str_len >= sdp_data_len) + goto too_short; + return 0; +too_short: + LOGP(DMNCC, LOGL_ERROR, "Short %s\n", label); + return -EINVAL; +} + int mncc_prim_check(const struct gsm_mncc *mncc_prim, unsigned int len) { if (len < sizeof(mncc_prim->msg_type)) { @@ -262,11 +288,7 @@ int mncc_prim_check(const struct gsm_mncc *mncc_prim, unsigned int len) case MNCC_RTP_FREE: case MNCC_RTP_CONNECT: case MNCC_RTP_CREATE: - if (len < sizeof(struct gsm_mncc_rtp)) { - LOGP(DMNCC, LOGL_ERROR, "Short MNCC RTP\n"); - return -EINVAL; - } - break; + return mncc_check_sdp_termination("MNCC RTP", mncc_prim, len, ((struct gsm_mncc_rtp*)mncc_prim)->sdp); case MNCC_LCHAN_MODIFY: case MNCC_FRAME_DROP: case MNCC_FRAME_RECV: @@ -279,10 +301,8 @@ int mncc_prim_check(const struct gsm_mncc *mncc_prim, unsigned int len) } break; default: - if (len < sizeof(struct gsm_mncc)) { - LOGP(DMNCC, LOGL_ERROR, "Short MNCC Signalling\n"); + if (mncc_check_sdp_termination("MNCC Signalling", mncc_prim, len, mncc_prim->sdp)) return -EINVAL; - } return mncc_prim_check_sign(mncc_prim); } return 0; diff --git a/src/libmsc/mncc_builtin.c b/src/libmsc/mncc_builtin.c index da096c64b..647420155 100644 --- a/src/libmsc/mncc_builtin.c +++ b/src/libmsc/mncc_builtin.c @@ -15,10 +15,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ @@ -89,10 +85,16 @@ static int mncc_setup_ind(struct gsm_call *call, goto out_reject; } - /* we currently only do speech */ - if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) { + /* we currently only do speech and CSD */ + switch (setup->bearer_cap.transfer) { + case GSM_MNCC_BCAP_SPEECH: + case GSM_MNCC_BCAP_AUDIO: + case GSM_MNCC_BCAP_FAX_G3: + case GSM_MNCC_BCAP_UNR_DIG: + break; + default: LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support " - "voice calls\n", call->callref); + "voice calls and CSD\n", call->callref); mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU, GSM48_CC_CAUSE_BEARER_CA_UNAVAIL); goto out_reject; @@ -303,7 +305,7 @@ int int_mncc_recv(struct gsm_network *net, struct msgb *msg) DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref, get_mncc_name(msg_type)); - switch(msg_type) { + switch (msg_type) { case MNCC_SETUP_IND: rc = mncc_setup_ind(call, arg); break; @@ -370,7 +372,8 @@ int int_mncc_recv(struct gsm_network *net, struct msgb *msg) rc = mncc_tx_to_cc(net, data); break; default: - LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref); + LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message '%s' unhandled\n", + callref, get_mncc_name(msg_type)); break; } diff --git a/src/libmsc/mncc_call.c b/src/libmsc/mncc_call.c index 5ca91d022..557f2d749 100644 --- a/src/libmsc/mncc_call.c +++ b/src/libmsc/mncc_call.c @@ -2,7 +2,7 @@ /* At the time of writing, this is only used for inter-MSC handover: forward a voice stream to a remote MSC. * Maybe it makes sense to also use it for all "normal" external call management at some point. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -35,6 +35,7 @@ #include <osmocom/msc/rtp_stream.h> #include <osmocom/msc/msub.h> #include <osmocom/msc/vlr.h> +#include <osmocom/msc/codec_mapping.h> struct osmo_fsm mncc_call_fsm; static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call); @@ -60,7 +61,7 @@ struct gsm_network *gsmnet = NULL; void mncc_call_fsm_init(struct gsm_network *net) { - osmo_fsm_register(&mncc_call_fsm); + OSMO_ASSERT(osmo_fsm_register(&mncc_call_fsm) == 0); gsmnet = net; } @@ -192,7 +193,7 @@ int mncc_call_set_rtp_stream(struct mncc_call *mncc_call, struct rtp_stream *rtp /* Disassociate the rtp_stream from this MNCC call instance, and clear the remote RTP IP:port info. * When the MNCC FSM ends for any reason, it will release the RTP stream (which usually triggers complete tear down of - * the call_leg and CC transaction). If the RTP stream should still remain in use, e.g. during Subseqent inter-MSC + * the call_leg and CC transaction). If the RTP stream should still remain in use, e.g. during Subsequent inter-MSC * Handover where this MNCC was a forwarding to a remote MSC that is no longer needed, this function must be called * before the MNCC FSM instance terminates. Call this *before* setting a new remote RTP address on the rtp_stream, since * this clears the rtp_stream->remote ip:port information. */ @@ -208,24 +209,25 @@ void mncc_call_detach_rtp_stream(struct mncc_call *mncc_call) static void mncc_call_tx_setup_ind(struct mncc_call *mncc_call) { - struct gsm_mncc mncc_msg = mncc_call->outgoing_req; - mncc_msg.msg_type = MNCC_SETUP_IND; - mncc_msg.callref = mncc_call->callref; + union mncc_msg mncc_msg; + mncc_msg.signal = mncc_call->outgoing_req; + mncc_msg.signal.msg_type = MNCC_SETUP_IND; + mncc_msg.signal.callref = mncc_call->callref; - OSMO_STRLCPY_ARRAY(mncc_msg.imsi, mncc_call->vsub->imsi); + OSMO_STRLCPY_ARRAY(mncc_msg.signal.imsi, mncc_call->vsub->imsi); if (!(mncc_call->outgoing_req.fields & MNCC_F_CALLING)) { /* No explicit calling number set, use the local subscriber */ - mncc_msg.fields |= MNCC_F_CALLING; - OSMO_STRLCPY_ARRAY(mncc_msg.calling.number, mncc_call->vsub->msisdn); + mncc_msg.signal.fields |= MNCC_F_CALLING; + OSMO_STRLCPY_ARRAY(mncc_msg.signal.calling.number, mncc_call->vsub->msisdn); } mncc_call->local_msisdn_present = true; - mncc_call->local_msisdn = mncc_msg.calling; + mncc_call->local_msisdn = mncc_msg.signal.calling; - rate_ctr_inc(&gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]); + rate_ctr_inc(rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_CALL_MO_SETUP)); - mncc_call_tx(mncc_call, (union mncc_msg*)&mncc_msg); + mncc_call_tx(mncc_call, &mncc_msg); } static void mncc_call_rx_setup_req(struct mncc_call *mncc_call, const struct gsm_mncc *incoming_req) @@ -256,45 +258,26 @@ static bool mncc_call_rx_rtp_create(struct mncc_call *mncc_call) return true; } - if (!osmo_sockaddr_str_is_set(&mncc_call->rtps->local)) { + if (!osmo_sockaddr_str_is_nonzero(&mncc_call->rtps->local)) { LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no local address\n"); return true; } - if (!mncc_call->rtps->codec_known) { + if (!mncc_call->rtps->codecs_known) { LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, but RTP stream has no codec set\n"); return true; } LOG_MNCC_CALL(mncc_call, LOGL_DEBUG, "Got RTP_CREATE, responding with " OSMO_SOCKADDR_STR_FMT " %s\n", OSMO_SOCKADDR_STR_FMT_ARGS(&mncc_call->rtps->local), - osmo_mgcpc_codec_name(mncc_call->rtps->codec)); + sdp_audio_codecs_to_str(&mncc_call->rtps->codecs)); /* Already know what RTP IP:port to tell the MNCC. Send it. */ return mncc_call_tx_rtp_create(mncc_call); } -/* Convert enum mgcp_codecs to an gsm_mncc_rtp->payload_msg_type value. */ -uint32_t mgcp_codec_to_mncc_payload_msg_type(enum mgcp_codecs codec) -{ - switch (codec) { - default: - /* disclaimer: i have no idea what i'm doing. */ - case CODEC_GSM_8000_1: - return GSM_TCHF_FRAME; - case CODEC_GSMEFR_8000_1: - return GSM_TCHF_FRAME_EFR; - case CODEC_GSMHR_8000_1: - return GSM_TCHH_FRAME; - case CODEC_AMR_8000_1: - case CODEC_AMRWB_16000_1: - //return GSM_TCHF_FRAME; - return GSM_TCH_FRAME_AMR; - } -} - static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call) { - if (!mncc_call->rtps || !osmo_sockaddr_str_is_set(&mncc_call->rtps->local)) { + if (!mncc_call->rtps || !osmo_sockaddr_str_is_nonzero(&mncc_call->rtps->local)) { mncc_call_error(mncc_call, "Cannot send RTP_CREATE, no local RTP address set up\n"); return false; } @@ -303,19 +286,26 @@ static bool mncc_call_tx_rtp_create(struct mncc_call *mncc_call) .rtp = { .msg_type = MNCC_RTP_CREATE, .callref = mncc_call->callref, - .port = rtp_local->port, }, }; - if (osmo_sockaddr_str_to_32n(rtp_local, &mncc_msg.rtp.ip)) { + if (osmo_sockaddr_str_to_sockaddr(rtp_local, &mncc_msg.rtp.addr)) { mncc_call_error(mncc_call, "Failed to compose IP address " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(rtp_local)); return false; } - if (mncc_call->rtps->codec_known) { - mncc_msg.rtp.payload_type = 0; /* ??? */ - mncc_msg.rtp.payload_msg_type = mgcp_codec_to_mncc_payload_msg_type(mncc_call->rtps->codec); + if (mncc_call->rtps->codecs_known) { + struct sdp_audio_codec *codec = &mncc_call->rtps->codecs.codec[0]; + const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name); + + if (!m) { + mncc_call_error(mncc_call, "Failed to resolve audio codec '%s'\n", + sdp_audio_codec_to_str(codec)); + return false; + } + mncc_msg.rtp.payload_type = codec->payload_type; + mncc_msg.rtp.payload_msg_type = m->mncc_payload_msg_type; } if (mncc_call_tx(mncc_call, &mncc_msg)) @@ -332,7 +322,7 @@ static bool mncc_call_rx_rtp_connect(struct mncc_call *mncc_call, const struct g return true; } - if (osmo_sockaddr_str_from_32n(&rtp, mncc_msg->ip, mncc_msg->port)) { + if (osmo_sockaddr_str_from_sockaddr(&rtp, &mncc_msg->addr)) { mncc_call_error(mncc_call, "Cannot RTP-CONNECT, invalid RTP IP:port in incoming MNCC message\n"); return false; } diff --git a/src/libmsc/mncc_sock.c b/src/libmsc/mncc_sock.c index 0a4e99b92..410449d6d 100644 --- a/src/libmsc/mncc_sock.c +++ b/src/libmsc/mncc_sock.c @@ -6,18 +6,14 @@ * All Rights Reserved * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. * */ @@ -65,7 +61,7 @@ int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg) /* Actually enqueue the message and mark socket write need */ msgb_enqueue(&net->upqueue, msg); - net->mncc_state->conn_bfd.when |= BSC_FD_WRITE; + osmo_fd_write_enable(&net->mncc_state->conn_bfd); return 0; } @@ -75,14 +71,14 @@ static void mncc_sock_close(struct mncc_sock_state *state) LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n"); + osmo_fd_unregister(bfd); close(bfd->fd); bfd->fd = -1; - osmo_fd_unregister(bfd); /* re-enable the generation of ACCEPT for new connections */ - state->listen_bfd.when |= BSC_FD_READ; + osmo_fd_read_enable(&state->listen_bfd); - /* release all exisitng calls */ + /* release all existing calls */ gsm0408_clear_all_trans(state->net, TRANS_CC); /* flush the queue */ @@ -146,7 +142,7 @@ static int mncc_sock_write(struct osmo_fd *bfd) msg = llist_entry(net->upqueue.next, struct msgb, list); mncc_prim = (struct gsm_mncc *)msg->data; - bfd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(bfd); /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ if (!msgb_length(msg)) { @@ -161,7 +157,7 @@ static int mncc_sock_write(struct osmo_fd *bfd) goto close; if (rc < 0) { if (errno == EAGAIN) { - bfd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(bfd); break; } goto close; @@ -185,12 +181,12 @@ static int mncc_sock_cb(struct osmo_fd *bfd, unsigned int flags) { int rc = 0; - if (flags & BSC_FD_READ) + if (flags & OSMO_FD_READ) rc = mncc_sock_read(bfd); if (rc < 0) return rc; - if (flags & BSC_FD_WRITE) + if (flags & OSMO_FD_WRITE) rc = mncc_sock_write(bfd); return rc; @@ -222,7 +218,7 @@ static void queue_hello(struct mncc_sock_state *mncc) hello->lchan_type_offset = offsetof(struct gsm_mncc, lchan_type); msgb_enqueue(&mncc->net->upqueue, msg); - mncc->conn_bfd.when |= BSC_FD_WRITE; + osmo_fd_write_enable(&mncc->conn_bfd); } /* accept a new connection */ @@ -245,16 +241,12 @@ static int mncc_sock_accept(struct osmo_fd *bfd, unsigned int flags) LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have " "another active connection ?!?\n"); /* We already have one MNCC app connected, this is all we support */ - state->listen_bfd.when &= ~BSC_FD_READ; + osmo_fd_read_disable(&state->listen_bfd); close(rc); return 0; } - conn_bfd->fd = rc; - conn_bfd->when = BSC_FD_READ; - conn_bfd->cb = mncc_sock_cb; - conn_bfd->data = state; - + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, mncc_sock_cb, state, 0); if (osmo_fd_register(conn_bfd) != 0) { LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n"); close(conn_bfd->fd); @@ -294,10 +286,7 @@ int mncc_sock_init(struct gsm_network *net, const char *sock_path) return -1; } - bfd->when = BSC_FD_READ; - bfd->cb = mncc_sock_accept; - bfd->data = state; - + osmo_fd_setup(bfd, bfd->fd, OSMO_FD_READ, mncc_sock_accept, state, 0); rc = osmo_fd_register(bfd); if (rc < 0) { LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc); diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c index 344b442cb..db1d9983a 100644 --- a/src/libmsc/msc_a.c +++ b/src/libmsc/msc_a.c @@ -1,6 +1,6 @@ /* Code to manage a subscriber's MSC-A role */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -36,6 +36,7 @@ #include <osmocom/msc/signal.h> #include <osmocom/msc/vlr.h> #include <osmocom/msc/transaction.h> +#include <osmocom/msc/transaction_cc.h> #include <osmocom/msc/ran_peer.h> #include <osmocom/msc/ran_msg_a.h> #include <osmocom/msc/ran_msg_iu.h> @@ -46,6 +47,8 @@ #include <osmocom/msc/call_leg.h> #include <osmocom/msc/rtp_stream.h> #include <osmocom/msc/msc_ho.h> +#include <osmocom/msc/codec_mapping.h> +#include <osmocom/msc/msc_vgcs.h> #define MSC_A_USE_WAIT_CLEAR_COMPLETE "wait-Clear-Complete" @@ -106,25 +109,46 @@ struct msc_a *msc_a_fi_priv(struct osmo_fsm_inst *fi) return fi->priv; } +bool msc_a_is_ciphering_to_be_attempted(const struct msc_a *msc_a) +{ + struct gsm_network *net = msc_a_net(msc_a); + bool is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU); + if (is_utran) + return net->uea_encryption_mask > (1 << OSMO_UTRAN_UEA0); + else + return net->a5_encryption_mask > 0x1; +} + +bool msc_a_is_ciphering_required(const struct msc_a *msc_a) +{ + struct gsm_network *net = msc_a_net(msc_a); + bool is_utran = (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU); + if (is_utran) + return net->uea_encryption_mask + && ((net->uea_encryption_mask & (1 << OSMO_UTRAN_UEA0)) == 0); + else + return net->a5_encryption_mask + && ((net->a5_encryption_mask & 0x1) == 0); +} + static void update_counters(struct osmo_fsm_inst *fi, bool conn_accepted) { struct msc_a *msc_a = fi->priv; struct gsm_network *net = msc_a_net(msc_a); switch (msc_a->complete_layer3_type) { case COMPLETE_LAYER3_LU: - rate_ctr_inc(&net->msc_ctrs->ctr[ - conn_accepted ? MSC_CTR_LOC_UPDATE_COMPLETED - : MSC_CTR_LOC_UPDATE_FAILED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, conn_accepted ? MSC_CTR_LOC_UPDATE_COMPLETED : MSC_CTR_LOC_UPDATE_FAILED)); break; case COMPLETE_LAYER3_CM_SERVICE_REQ: - rate_ctr_inc(&net->msc_ctrs->ctr[ - conn_accepted ? MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED - : MSC_CTR_CM_SERVICE_REQUEST_REJECTED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, conn_accepted ? MSC_CTR_CM_SERVICE_REQUEST_ACCEPTED : MSC_CTR_CM_SERVICE_REQUEST_REJECTED)); break; case COMPLETE_LAYER3_PAGING_RESP: - rate_ctr_inc(&net->msc_ctrs->ctr[ - conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED - : MSC_CTR_PAGING_RESP_REJECTED]); + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, conn_accepted ? MSC_CTR_PAGING_RESP_ACCEPTED : MSC_CTR_PAGING_RESP_REJECTED)); + break; + case COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ: + rate_ctr_inc(rate_ctr_group_get_ctr(net->msc_ctrs, + conn_accepted ? MSC_CTR_CM_RE_ESTABLISH_REQ_ACCEPTED + : MSC_CTR_CM_RE_ESTABLISH_REQ_REJECTED)); break; default: break; @@ -138,6 +162,12 @@ static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_acce update_counters(fi, conn_accepted); + if (conn_accepted) { + /* Record the Cell ID seen in Complete Layer 3 Information in the VLR, so that it also shows in vty + * 'show' output. */ + vsub->cgi = msc_a->via_cell; + } + /* Trigger transactions that we paged for */ if (msc_a->complete_layer3_type == COMPLETE_LAYER3_PAGING_RESP) { if (conn_accepted) @@ -151,6 +181,15 @@ static void evaluate_acceptance_outcome(struct osmo_fsm_inst *fi, bool conn_acce if (msc_a->complete_layer3_type == COMPLETE_LAYER3_LU) msc_a_put(msc_a, MSC_A_USE_LOCATION_UPDATING); + + if (conn_accepted && msc_a->complete_layer3_type == COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ) { + /* Trigger new Assignment to recommence the voice call. A little dance here because normally we verify + * that no CC trans is already active. */ + struct gsm_trans *cc_trans = msc_a->cc.active_trans; + msc_a->cc.active_trans = NULL; + osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, cc_trans); + msc_a_try_call_assignment(cc_trans); + } } bool msc_a_is_accepted(const struct msc_a *msc_a) @@ -292,6 +331,14 @@ int msc_a_vlr_set_cipher_mode(void *_msc_a, bool umts_aka, bool retrieve_imeisv) return msc_a_ran_enc_ciphering(msc_a, umts_aka, retrieve_imeisv); } +static uint8_t filter_a5(uint8_t a5_mask, bool umts_aka) +{ + /* With GSM AKA: allow A5/0, 1, 3 = 0b00001011 = 0xb. + * UMTS aka: allow A5/0, 1, 3, 4 = 0b00011011 = 0x1b. + */ + return a5_mask & (umts_aka ? 0x1b : 0x0b); +} + static int msc_a_ran_enc_ciphering(struct msc_a *msc_a, bool umts_aka, bool retrieve_imeisv) { struct gsm_network *net; @@ -321,11 +368,14 @@ static int msc_a_ran_enc_ciphering(struct msc_a *msc_a, bool umts_aka, bool retr .geran = { .umts_aka = umts_aka, .retrieve_imeisv = retrieve_imeisv, - .a5_encryption_mask = net->a5_encryption_mask, + .a5_encryption_mask = filter_a5(net->a5_encryption_mask, umts_aka), /* for ran_a.c to store the GERAN key that is actually used */ .chosen_key = &msc_a->geran_encr, }, + .utran = { + .uea_encryption_mask = net->uea_encryption_mask, + }, }, }; @@ -337,8 +387,12 @@ static int msc_a_ran_enc_ciphering(struct msc_a *msc_a, bool umts_aka, bool retr } if (msc_a->geran_encr.key_len) - LOG_MSC_A(msc_a, LOGL_DEBUG, "RAN encoding chose ciphering key %s\n", - osmo_hexdump_nospc(msc_a->geran_encr.key, msc_a->geran_encr.key_len)); + LOG_MSC_A(msc_a, LOGL_DEBUG, "RAN encoding chose ciphering: A5/%d kc %s kc128 %s\n", + msc_a->geran_encr.alg_id - 1, + osmo_hexdump_nospc_c(OTC_SELECT, msc_a->geran_encr.key, msc_a->geran_encr.key_len), + msc_a->geran_encr.kc128_present ? + osmo_hexdump_nospc_c(OTC_SELECT, msc_a->geran_encr.kc128, sizeof(msc_a->geran_encr.kc128)) + : "-"); return 0; } @@ -435,6 +489,16 @@ static bool msc_a_fsm_has_active_transactions(struct osmo_fsm_inst *fi) __func__); return true; } + if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_GCC)) { + LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still awaiting MO GCC request after a CM Service Request\n", + __func__); + return true; + } + if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_BCC)) { + LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still awaiting MO BCC request after a CM Service Request\n", + __func__); + return true; + } if (osmo_use_count_by(&msc_a->use_count, MSC_A_USE_CM_SERVICE_SMS)) { LOG_MSC_A(msc_a, LOGL_DEBUG, "%s: still awaiting MO SMS after a CM Service Request\n", __func__); @@ -511,23 +575,126 @@ static void msc_a_fsm_authenticated(struct osmo_fsm_inst *fi, uint32_t event, vo } } +static struct call_leg *msc_a_ensure_call_leg(struct msc_a *msc_a, struct gsm_trans *for_cc_trans) +{ + struct call_leg *cl = msc_a->cc.call_leg; + struct gsm_network *net = msc_a_net(msc_a); + + /* Ensure that events about RTP endpoints coming from the msc_a->cc.call_leg know which gsm_trans to abort on + * error */ + if (!msc_a->cc.active_trans) + msc_a->cc.active_trans = for_cc_trans; + if (msc_a->cc.active_trans != for_cc_trans) { + LOG_TRANS(for_cc_trans, LOGL_ERROR, + "Cannot create call leg, another trans is already active for this conn\n"); + return NULL; + } + + if (!cl) { + cl = msc_a->cc.call_leg = call_leg_alloc(msc_a->c.fi, + MSC_EV_CALL_LEG_TERM, + MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, + MSC_EV_CALL_LEG_RTP_COMPLETE); + OSMO_ASSERT(cl); + + if (net->use_osmux != OSMUX_USAGE_OFF) { + struct msc_i *msc_i = msc_a_msc_i(msc_a); + if (msc_i->c.remote_to) { + /* TODO: investigate what to do in this case */ + LOG_MSC_A(msc_a, LOGL_ERROR, "Osmux not yet supported for inter-MSC"); + } else { + cl->ran_peer_supports_osmux = msc_i->ran_conn->ran_peer->remote_supports_osmux; + } + } + + } + return cl; +} + +int msc_a_ensure_cn_local_rtp(struct msc_a *msc_a, struct gsm_trans *cc_trans) +{ + struct call_leg *cl; + struct rtp_stream *rtp_to_ran; + + cl = msc_a_ensure_call_leg(msc_a, cc_trans); + if (!cl) + return -EINVAL; + rtp_to_ran = cl->rtp[RTP_TO_RAN]; + + if (call_leg_local_ip(cl, RTP_TO_CN)) { + /* Already has an RTP address and port towards the CN, continue right away. */ + return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_CN]); + } + + /* No CN RTP address available yet, ask the MGW to create one. + * Set a codec to be used: if Assignment on the RAN side is already done, take the same codec as the RTP_TO_RAN. + * If no RAN side RTP is established, try to guess a preliminary codec from SDP -- before Assignment, picking a + * codec from the SDP is more politeness/avoiding confusion than necessity. The actual codec to be used would be + * determined later. If no codec could be determined, pass none for the time being. */ + return call_leg_ensure_ci(cl, RTP_TO_CN, cc_trans->call_id, cc_trans, + rtp_to_ran->codecs_known ? &rtp_to_ran->codecs : NULL, NULL); +} + /* The MGW has given us a local IP address for the RAN side. Ready to start the Assignment of a voice channel. */ -static void msc_a_call_leg_ran_local_addr_available(struct msc_a *msc_a) +void msc_a_tx_assignment_cmd(struct msc_a *msc_a) { struct ran_msg msg; struct gsm_trans *cc_trans = msc_a->cc.active_trans; struct gsm0808_channel_type channel_type; - /* Once a CI is known, we could also CRCX the CN side of the MGW endpoint, but it makes sense to wait for the - * codec to be determined by the Assignment Complete message, first. */ + if (!cc_trans) { + LOG_MSC_A(msc_a, LOGL_ERROR, "No CC transaction active\n"); + call_leg_release(msc_a->cc.call_leg); + return; + } + + trans_cc_filter_run(cc_trans); + LOG_TRANS(cc_trans, LOGL_DEBUG, "Sending Assignment Command\n"); + + switch (cc_trans->bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + if (!cc_trans->cc.local.audio_codecs.count) { + LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible, no matching codec: %s\n", + codec_filter_to_str(&cc_trans->cc.codecs, &cc_trans->cc.local, &cc_trans->cc.remote)); + call_leg_release(msc_a->cc.call_leg); + return; + } + + /* Compose 48.008 Channel Type from the current set of codecs + * determined from both local and remote codec capabilities. */ + if (sdp_audio_codecs_to_gsm0808_channel_type(&channel_type, &cc_trans->cc.local.audio_codecs)) { + LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose Channel Type (Permitted Speech) from codecs: %s\n", + codec_filter_to_str(&cc_trans->cc.codecs, &cc_trans->cc.local, &cc_trans->cc.remote)); + trans_free(cc_trans); + return; + } + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (!cc_trans->cc.local.bearer_services.count) { + LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible, no matching bearer service: %s\n", + csd_filter_to_str(&cc_trans->cc.csd, &cc_trans->cc.local, &cc_trans->cc.remote)); + call_leg_release(msc_a->cc.call_leg); + return; + } - if (mncc_bearer_cap_to_channel_type(&channel_type, &cc_trans->bearer_cap)) { - LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose Channel Type from bearer capabilities\n"); - /* FIXME: ERROR HANDLING */ + /* Compose 48.008 Channel Type from the current set of bearer + * services determined from local and remote capabilities. */ + if (csd_bs_list_to_gsm0808_channel_type(&channel_type, &cc_trans->cc.local.bearer_services)) { + LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot compose channel type from: %s\n", + csd_filter_to_str(&cc_trans->cc.csd, &cc_trans->cc.local, &cc_trans->cc.remote)); + return; + } + break; + default: + LOG_TRANS(cc_trans, LOGL_ERROR, "Assignment not possible for information transfer capability %d\n", + cc_trans->bearer_cap.transfer); + call_leg_release(msc_a->cc.call_leg); return; } - /* The RAN side RTP address is known, so the voice Assignment can commence. */ + /* The RAN side RTP address is known, so the voice/CSD Assignment can commence. */ msg = (struct ran_msg){ .msg_type = RAN_MSG_ASSIGNMENT_COMMAND, .assignment_command = { @@ -535,20 +702,14 @@ static void msc_a_call_leg_ran_local_addr_available(struct msc_a *msc_a) .channel_type = &channel_type, .osmux_present = msc_a->cc.call_leg->rtp[RTP_TO_RAN]->use_osmux, .osmux_cid = msc_a->cc.call_leg->rtp[RTP_TO_RAN]->local_osmux_cid, + .call_id_present = true, + .call_id = cc_trans->call_id, + .lcls = cc_trans->cc.lcls, }, }; if (msc_a_ran_down(msc_a, MSC_ROLE_I, &msg)) { LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot send Assignment\n"); - /* FIXME: ERROR HANDLING */ - return; - } -} - -static void msc_a_call_leg_cn_local_addr_available(struct msc_a *msc_a, struct gsm_trans *cc_trans) -{ - if (gsm48_tch_rtp_create(cc_trans)) { - LOG_MSC_A(msc_a, LOGL_ERROR, "Cannot inform MNCC of RTP address\n"); - /* FIXME: ERROR HANDLING */ + trans_free(cc_trans); return; } } @@ -627,16 +788,26 @@ static void msc_a_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, vo LOG_MSC_A(msc_a, LOGL_ERROR, "Invalid data for %s\n", osmo_fsm_event_name(fi->fsm, event)); return; } + if (!msc_a->cc.call_leg) { + LOG_MSC_A(msc_a, LOGL_ERROR, "No call leg active\n"); + return; + } + if (!osmo_sockaddr_str_is_nonzero(&rtps->local)) { + LOG_MSC_A(msc_a, LOGL_ERROR, "Invalid RTP address received from MGW: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&rtps->local)); + call_leg_release(msc_a->cc.call_leg); + return; + } LOG_MSC_A(msc_a, LOGL_DEBUG, "MGW endpoint's RTP address available for the CI %s: " OSMO_SOCKADDR_STR_FMT " (osmux=%s:%d)\n", rtp_direction_name(rtps->dir), OSMO_SOCKADDR_STR_FMT_ARGS(&rtps->local), rtps->use_osmux ? "yes" : "no", rtps->local_osmux_cid); switch (rtps->dir) { case RTP_TO_RAN: - msc_a_call_leg_ran_local_addr_available(msc_a); + msc_a_tx_assignment_cmd(msc_a); return; case RTP_TO_CN: - msc_a_call_leg_cn_local_addr_available(msc_a, rtps->for_trans); + cc_on_cn_local_rtp_port_known(rtps->for_trans); return; default: LOG_MSC_A(msc_a, LOGL_ERROR, "Invalid data for %s\n", osmo_fsm_event_name(fi->fsm, event)); @@ -715,6 +886,8 @@ static void msc_a_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_ MSC_A_USE_CM_SERVICE_CC, MSC_A_USE_CM_SERVICE_SMS, MSC_A_USE_CM_SERVICE_SS, + MSC_A_USE_CM_SERVICE_GCC, + MSC_A_USE_CM_SERVICE_BCC, MSC_A_USE_PAGING_RESPONSE, }; @@ -755,6 +928,9 @@ static void msc_a_fsm_releasing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_ struct ran_msg msg = { .msg_type = RAN_MSG_CLEAR_COMMAND, .clear_command = { + /* "Call Control" is the only cause code listed in 3GPP TS 48.008 3.2.1.21 CLEAR COMMAND + * that qualifies for a normal release situation. (OS#4664) */ + .gsm0808_cause = GSM0808_CAUSE_CALL_CONTROL, .csfb_ind = (vsub && vsub->sgs_fsm->state == SGS_UE_ST_ASSOCIATED), }, }; @@ -823,6 +999,7 @@ static void msc_a_fsm_released(struct osmo_fsm_inst *fi, uint32_t event, void *d void msc_a_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) { struct msc_a *msc_a = msc_a_fi_priv(fi); + struct vlr_subscr *vsub = msc_a_vsub(msc_a); trans_conn_closed(msc_a); @@ -830,6 +1007,10 @@ void msc_a_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) LOG_MSC_A(msc_a, LOGL_ERROR, "Deallocating active transactions failed\n"); LOG_MSC_A_CAT(msc_a, DREF, LOGL_DEBUG, "max total use count was %d\n", msc_a->max_total_use_count); + + /* Invalidate the active conn in VLR subscriber state, if any. */ + if (vsub && vsub->msc_conn_ref == msc_a) + vsub->msc_conn_ref = NULL; } const struct value_string msc_a_fsm_event_names[] = { @@ -969,6 +1150,7 @@ static const struct osmo_fsm_state msc_a_fsm_states[] = { | S(MSC_EV_CALL_LEG_TERM) | S(MSC_MNCC_EV_CALL_ENDED) | S(MSC_A_EV_HANDOVER_END) + | S(MSC_A_EV_CN_CLOSE) , .out_state_mask = 0 | S(MSC_A_ST_RELEASED) @@ -1058,6 +1240,7 @@ const struct value_string complete_layer3_type_names[] = { { COMPLETE_LAYER3_LU, "LU" }, { COMPLETE_LAYER3_CM_SERVICE_REQ, "CM_SERVICE_REQ" }, { COMPLETE_LAYER3_PAGING_RESP, "PAGING_RESP" }, + { COMPLETE_LAYER3_CM_RE_ESTABLISH_REQ, "CM_RE_ESTABLISH_REQ" }, { 0, NULL } }; @@ -1084,9 +1267,9 @@ const struct value_string complete_layer3_type_names[] = { /* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */ -void msc_a_update_id_from_mi(struct msc_a *msc_a, const uint8_t mi[], uint8_t mi_len) +void msc_a_update_id_from_mi(struct msc_a *msc_a, const struct osmo_mobile_identity *mi) { - _msc_a_update_id(msc_a, "%s", osmo_mi_name(mi, mi_len)); + _msc_a_update_id(msc_a, "%s", osmo_mobile_identity_to_str_c(OTC_SELECT, mi)); } /* Update msc_a->fi id string from current msc_a->vsub and msc_a->complete_layer3_type. */ @@ -1210,6 +1393,10 @@ int msc_a_up_l3(struct msc_a *msc_a, struct msgb *msg) #endif switch (pdisc) { + case GSM48_PDISC_GROUP_CC: + case GSM48_PDISC_BCAST_CC: + rc = gsm44068_rcv_bcc_gcc(msc_a, NULL, msg); + break; case GSM48_PDISC_CC: rc = gsm0408_rcv_cc(msc_a, msg); break; @@ -1246,15 +1433,29 @@ int msc_a_up_l3(struct msc_a *msc_a, struct msgb *msg) static void msc_a_up_call_assignment_complete(struct msc_a *msc_a, const struct ran_msg *ac) { - struct gsm_trans *cc_trans = msc_a->cc.active_trans; + struct gsm_trans *cc_trans = msc_a->cc.active_trans, *gcc_trans; struct rtp_stream *rtps_to_ran = msc_a->cc.call_leg ? msc_a->cc.call_leg->rtp[RTP_TO_RAN] : NULL; + const struct gsm0808_speech_codec *codec_if_known = ac->assignment_complete.codec_present ? + &ac->assignment_complete.codec : NULL; + + /* For a voice group call, handling is performed by VGCS FSM */ + gcc_trans = trans_find_by_type(msc_a, TRANS_GCC); + if (gcc_trans) { + vgcs_vbs_caller_assign_cpl(gcc_trans); + return; + } + gcc_trans = trans_find_by_type(msc_a, TRANS_BCC); + if (gcc_trans) { + vgcs_vbs_caller_assign_cpl(gcc_trans); + return; + } if (!rtps_to_ran) { LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but no RTP stream is set up\n"); return; } if (!cc_trans) { - LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but CC transaction is active\n"); + LOG_MSC_A(msc_a, LOGL_ERROR, "Rx Assignment Complete, but no CC transaction is active\n"); return; } @@ -1265,24 +1466,76 @@ static void msc_a_up_call_assignment_complete(struct msc_a *msc_a, const struct return; } - /* Update RAN-side endpoint CI: */ - rtp_stream_set_codec(rtps_to_ran, ac->assignment_complete.codec); + if (codec_if_known) { + const struct codec_mapping *codec_assigned; + + /* Check for unexpected codec with CSD */ + switch (cc_trans->bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (codec_if_known->type == GSM0808_SCT_CSD) + break; /* we're good */ + LOG_TRANS(cc_trans, LOGL_ERROR, "Unexpected codec in Assignment Complete for CSD: %s\n", + gsm0808_speech_codec_type_name(codec_if_known->type)); + call_leg_release(msc_a->cc.call_leg); + return; + default: + break; + } + + /* For 2G: + * - The Assignment Complete has returned a specific codec (e.g. FR3 for AMR FR). + * - Set this codec at the MGW endpoint facing the RAN. + * - Also set this codec at the MGW endpoint facing the CN -- we require an exact match on both call + * legs. + * - TODO: be aware of transcoding that the MGW is capable of, e.g. AMR octet-aligned to AMR + * bandwidth-efficient... + * + * For 3G: + * - ran_infra->force_mgw_codecs_to_ran sets VND.3GPP.IUFP as single codec at the MGW towards RAN. + * - ran_msg_iu.c always returns FR3 (AMR FR) for the assigned codec. Set that at the MGW towards CN. + * - So the MGW decapsulates IuUP <-> AMR + */ + codec_assigned = codec_mapping_by_gsm0808_speech_codec_type(codec_if_known->type); + /* TODO: use codec_mapping_by_gsm0808_speech_codec() to also match on codec_if_known->cfg */ + if (!codec_assigned) { + LOG_TRANS(cc_trans, LOGL_ERROR, "Unknown codec in Assignment Complete: %s\n", + gsm0808_speech_codec_type_name(codec_if_known->type)); + call_leg_release(msc_a->cc.call_leg); + return; + } + + /* Update RAN-side endpoint CI from Assignment result -- unless it is forced by the ran_infra, in which + * case it remains unchanged as passed to the earlier call of call_leg_ensure_ci(). */ + if (msc_a->c.ran->force_mgw_codecs_to_ran.count == 0) + rtp_stream_set_one_codec(rtps_to_ran, &codec_assigned->sdp); + + /* Update codec filter with Assignment result, for the CN side */ + cc_trans->cc.codecs.assignment = codec_assigned->sdp; + } else { + /* No codec passed in Assignment Complete, set 'codecs.assignment' to none. */ + cc_trans->cc.codecs.assignment = (struct sdp_audio_codec){}; + LOG_TRANS(cc_trans, LOGL_INFO, "Assignment Complete without voice codec\n"); + } + rtp_stream_set_remote_addr(rtps_to_ran, &ac->assignment_complete.remote_rtp); if (rtps_to_ran->use_osmux) rtp_stream_set_remote_osmux_cid(rtps_to_ran, ac->assignment_complete.osmux_cid); - rtp_stream_commit(rtps_to_ran); - /* Setup CN side endpoint CI: - * Now that - * - the first CI has been created and a definitive endpoint name is assigned to the call_leg's MGW - * endpoint, - * - the Assignment has chosen a speech codec - * go on to create the CN side RTP stream's CI. */ - if (call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_CN, cc_trans->callref, cc_trans, - &ac->assignment_complete.codec, NULL)) { - LOG_MSC_A_CAT(msc_a, DCC, LOGL_ERROR, "Error creating MGW CI towards CN\n"); + /* Remember the Codec List (BSS Supported) */ + if (ac->assignment_complete.codec_list_bss_supported) + codec_filter_set_bss(&cc_trans->cc.codecs, ac->assignment_complete.codec_list_bss_supported); + + trans_cc_filter_run(cc_trans); + LOG_TRANS(cc_trans, LOGL_INFO, "Assignment Complete: RAN: %s, CN: %s\n", + sdp_audio_codecs_to_str(&rtps_to_ran->codecs), + sdp_audio_codecs_to_str(&cc_trans->cc.local.audio_codecs)); + + if (cc_on_assignment_done(cc_trans)) { + /* If an error occurred, it was logged in cc_assignment_done() */ call_leg_release(msc_a->cc.call_leg); return; } @@ -1299,6 +1552,18 @@ static void msc_a_up_call_assignment_failure(struct msc_a *msc_a, const struct r return; } + /* For a voice group call, release is performed by VGCS FSM */ + trans = trans_find_by_type(msc_a, TRANS_GCC); + if (trans) { + vgcs_vbs_caller_assign_fail(trans); + return; + } + trans = trans_find_by_type(msc_a, TRANS_BCC); + if (trans) { + vgcs_vbs_caller_assign_fail(trans); + return; + } + /* Otherwise, a silent call might be active */ trans = trans_find_by_type(msc_a, TRANS_SILENT_CALL); if (trans) { @@ -1323,7 +1588,7 @@ static void msc_a_up_classmark_update(struct msc_a *msc_a, const struct osmo_gsm dst = &vsub->classmark; } - LOG_MSC_A(msc_a, LOGL_DEBUG, "A5 capabilities recived from Classmark Update: %s\n", + LOG_MSC_A(msc_a, LOGL_DEBUG, "A5 capabilities received from Classmark Update: %s\n", osmo_gsm48_classmark_a5_name(classmark)); osmo_gsm48_classmark_update(dst, classmark); @@ -1351,16 +1616,33 @@ static int msc_a_up_ho(struct msc_a *msc_a, const struct msc_a_ran_dec_data *d, int msc_a_ran_dec_from_msc_i(struct msc_a *msc_a, struct msc_a_ran_dec_data *d) { struct vlr_subscr *vsub = msc_a_vsub(msc_a); + struct gsm_network *net = msc_a_net(msc_a); const struct ran_msg *msg = d->ran_dec; int rc = -99; switch (msg->msg_type) { case RAN_MSG_COMPL_L3: + /* In case the cell_id from Complete Layer 3 Information lacks a PLMN, write the configured PLMN code + * into msc_a->via_cell. Then overwrite with those bits obtained from Complete Layer 3 Information. */ msc_a->via_cell = (struct osmo_cell_global_id){ .lai.plmn = msc_a_net(msc_a)->plmn, }; gsm0808_cell_id_to_cgi(&msc_a->via_cell, msg->compl_l3.cell_id); + + /* If a codec list was sent along in the RAN_MSG_COMPL_L3, remember it for any upcoming codec + * resolution. */ + if (msg->compl_l3.codec_list_bss_supported) { + msc_a->cc.compl_l3_codec_list_bss_supported = *msg->compl_l3.codec_list_bss_supported; + if (log_check_level(msc_a->c.ran->log_subsys, LOGL_DEBUG)) { + struct sdp_audio_codecs ac = {}; + sdp_audio_codecs_from_speech_codec_list(&ac, &msc_a->cc.compl_l3_codec_list_bss_supported); + LOG_MSC_A(msc_a, LOGL_DEBUG, "Complete Layer 3: Codec List (BSS Supported): %s\n", + sdp_audio_codecs_to_str(&ac)); + } + } + + /* Submit the Complete Layer 3 Information DTAP */ rc = msc_a_up_l3(msc_a, msg->compl_l3.msg); if (!rc) { struct ran_conn *conn = msub_ran_conn(msc_a->c.msub); @@ -1404,7 +1686,25 @@ int msc_a_ran_dec_from_msc_i(struct msc_a *msc_a, struct msc_a_ran_dec_data *d) msc_a->geran_encr.alg_id = msg->cipher_mode_complete.alg_id; LOG_MSC_A(msc_a, LOGL_DEBUG, "Cipher Mode Complete: chosen encryption algorithm: A5/%u\n", msc_a->geran_encr.alg_id - 1); - }; + } + + if (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU) { + int16_t utran_encryption; + + /* utran: ensure chosen ciphering mode is allowed + * If the IE is missing (utran_encryption == -1), parse it as no encryption */ + utran_encryption = msg->cipher_mode_complete.utran_encryption; + if (utran_encryption == -1) + utran_encryption = 0; + if ((net->uea_encryption_mask & (1 << utran_encryption)) == 0) { + /* cipher disallowed */ + LOG_MSC_A(msc_a, LOGL_ERROR, "Cipher Mode Complete: RNC chosen forbidden ciphering UEA%d\n", + msg->cipher_mode_complete.utran_encryption); + vlr_subscr_rx_ciph_res(vsub, VLR_CIPH_REJECT); + rc = 0; + break; + } + } vlr_subscr_rx_ciph_res(vsub, VLR_CIPH_COMPL); rc = 0; @@ -1455,6 +1755,13 @@ int msc_a_ran_dec_from_msc_i(struct msc_a *msc_a, struct msc_a_ran_dec_data *d) rc = msc_a_up_ho(msc_a, d, MSC_HO_EV_RX_FAILURE); break; + case RAN_MSG_LCLS_STATUS: + /* The BSS sends us LCLS_STATUS. We do nothing for now, but it is not an error. */ + LOG_MSC_A(msc_a, LOGL_DEBUG, "LCLS_STATUS (%s) received from MSC-I\n", + gsm0808_lcls_status_name(msg->lcls_status.status)); + rc = 0; + break; + default: LOG_MSC_A(msc_a, LOGL_ERROR, "Message from MSC-I not implemented: %s\n", ran_msg_type_name(msg->msg_type)); rc = -ENOTSUP; @@ -1463,6 +1770,136 @@ int msc_a_ran_dec_from_msc_i(struct msc_a *msc_a, struct msc_a_ran_dec_data *d) return rc; } +static int msc_a_rx_vgcs_bss_decoded(struct osmo_fsm_inst *caller_fi, void *caller_data, const struct ran_msg *msg) +{ + struct vgcs_bss *bss = caller_data; + struct msc_a *msc_a = (bss->trans) ? bss->trans->msc_a : NULL; + int rc = 0; + + switch (msg->msg_type) { + case RAN_MSG_VGCS_VBS_SETUP_ACK: + /* The BSS accepts VGCS/VBS and sends us supported features. */ + vgcs_vbs_setup_ack(bss, msg); + break; + case RAN_MSG_VGCS_VBS_SETUP_REFUSE: + /* The BSS refuses VGCS/VBS. */ + vgcs_vbs_setup_refuse(bss, msg); + break; + case RAN_MSG_UPLINK_REQUEST: + /* A mobile station requests the uplink on a VGCS channel. */ + vgcs_uplink_request(bss, msg); + break; + case RAN_MSG_UPLINK_REQUEST_CNF: + /* The uplink on a VGCS channel has been established. */ + vgcs_uplink_request_cnf(bss, msg); + break; + case RAN_MSG_UPLINK_APPLICATION_DATA: + /* Application data received on the uplink of a VGCS channel. */ + vgcs_app_data(bss, msg); + break; + case RAN_MSG_DTAP: + /* BSS confirms the release of the channel. */ + vgcs_bss_dtap(bss, msg); + break; + case RAN_MSG_UPLINK_RELEASE_IND: + /* A mobile station releases the uplink on a VGCS channel. */ + vgcs_uplink_release_ind(bss, msg); + break; + case RAN_MSG_CLEAR_REQUEST: + /* BSS indicated that the channel has been released. */ + vgcs_vbs_clear_req(bss, msg); + break; + case RAN_MSG_CLEAR_COMPLETE: + /* BSS confirms the release of the channel. */ + vgcs_vbs_clear_cpl(bss, msg); + break; + default: + LOG_MSC_A(msc_a, LOGL_ERROR, "VGCS message from BSS not implemented: %s\n", + ran_msg_type_name(msg->msg_type)); + rc = -ENOTSUP; + break; + } + return rc; +} + +int msc_a_rx_vgcs_bss(struct vgcs_bss *bss, struct ran_conn *from_conn, struct msgb *msg) +{ + struct ran_dec ran_dec; + + /* Feed through the decoding mechanism ran_msg. The decoded message arrives in msc_a_rx_vgcs_decoded() */ + ran_dec = (struct ran_dec) { + .caller_data = bss, + .decode_cb = msc_a_rx_vgcs_bss_decoded, + }; + struct ran_peer *ran_peer = from_conn->ran_peer; + struct ran_infra *ran = ran_peer->sri->ran; + if (!ran->ran_dec_l2) { + LOGP(DMSC, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n", + osmo_rat_type_name(ran->type)); + return -ENOTSUP; + } + return ran->ran_dec_l2(&ran_dec, msg); +} + +static int msc_a_rx_vgcs_cell_decoded(struct osmo_fsm_inst *caller_fi, void *caller_data, const struct ran_msg *msg) +{ + struct vgcs_bss_cell *cell = caller_data; + struct msc_a *msc_a = (cell->bss && cell->bss->trans) ? cell->bss->trans->msc_a : NULL; + int rc = 0; + + switch (msg->msg_type) { + case RAN_MSG_VGCS_VBS_ASSIGN_RES: + /* The BSS accepts VGCS/VBS channel assignment. */ + vgcs_vbs_assign_result(cell, msg); + break; + case RAN_MSG_VGCS_VBS_ASSIGN_FAIL: + /* The BSS refuses VGCS/VBS channel assignment. */ + vgcs_vbs_assign_fail(cell, msg); + break; + case RAN_MSG_VGCS_VBS_QUEUING_IND: + /* The BSS needs more time for VGCS/VBS channel assignment. */ + vgcs_vbs_queuing_ind(cell); + break; + case RAN_MSG_VGCS_VBS_ASSIGN_STATUS: + /* The BSS gives cell status about VGCS/VBS channel. */ + vgcs_vbs_assign_status(cell, msg); + break; + case RAN_MSG_CLEAR_REQUEST: + /* BSS indicated that the channel has been released. */ + vgcs_vbs_clear_req_channel(cell, msg); + break; + case RAN_MSG_CLEAR_COMPLETE: + /* BSS confirms the release of the channel. */ + vgcs_vbs_clear_cpl_channel(cell, msg); + break; + default: + LOG_MSC_A(msc_a, LOGL_ERROR, "Message from BSS leg not implemented: %s\n", + ran_msg_type_name(msg->msg_type)); + rc = -ENOTSUP; + break; + } + return rc; +} + +int msc_a_rx_vgcs_cell(struct vgcs_bss_cell *cell, struct ran_conn *from_conn, struct msgb *msg) +{ + struct ran_dec ran_dec; + + /* Feed through the decoding mechanism ran_msg. The decoded message arrives in msc_a_rx_vgcs_decoded() */ + ran_dec = (struct ran_dec) { + .caller_data = cell, + .decode_cb = msc_a_rx_vgcs_cell_decoded, + }; + struct ran_peer *ran_peer = from_conn->ran_peer; + struct ran_infra *ran = ran_peer->sri->ran; + if (!ran->ran_dec_l2) { + LOGP(DMSC, LOGL_ERROR, "No ran_dec_l2() defined for RAN type %s\n", + osmo_rat_type_name(ran->type)); + return -ENOTSUP; + } + return ran->ran_dec_l2(&ran_dec, msg); +} + static int msc_a_ran_dec_from_msc_t(struct msc_a *msc_a, struct msc_a_ran_dec_data *d) { struct msc_t *msc_t = msc_a_msc_t(msc_a); @@ -1568,21 +2005,18 @@ int _msc_a_msg_down(struct msc_a *msc_a, enum msc_role to_role, uint32_t to_role .an_proto = msc_a->c.ran->an_proto, .msg = msc_role_ran_encode(msc_a->c.fi, ran_msg), }; - int rc; if (!an_apdu.msg) return -EIO; - rc = _msub_role_dispatch(msc_a->c.msub, to_role, to_role_event, &an_apdu, file, line); - msgb_free(an_apdu.msg); - return rc; + return _msub_role_dispatch(msc_a->c.msub, to_role, to_role_event, &an_apdu, file, line); } int msc_a_tx_dtap_to_i(struct msc_a *msc_a, struct msgb *dtap) { struct ran_msg ran_msg; + struct gsm48_hdr *gh = msgb_l3(dtap) ? : dtap->data; + uint8_t pdisc = gsm48_hdr_pdisc(gh); if (!msc_a) { - struct gsm48_hdr *gh = msgb_l3(dtap) ? : dtap->data; - uint8_t pdisc = gsm48_hdr_pdisc(gh); LOGP(DMSC, LOGL_ERROR, "Attempt to send DTAP to NULL MSC-A, dropping message: %s %s\n", gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh))); msgb_free(dtap); @@ -1594,6 +2028,9 @@ int msc_a_tx_dtap_to_i(struct msc_a *msc_a, struct msgb *dtap) return sgs_iface_tx_dtap_ud(msc_a, dtap); } + LOG_MSC_A(msc_a, LOGL_DEBUG, "Sending DTAP: %s %s\n", + gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh))); + ran_msg = (struct ran_msg){ .msg_type = RAN_MSG_DTAP, .dtap = dtap, @@ -1612,57 +2049,68 @@ struct msc_a *msc_a_for_vsub(const struct vlr_subscr *vsub, bool valid_conn_only int msc_tx_common_id(struct msc_a *msc_a, enum msc_role to_role) { struct vlr_subscr *vsub = msc_a_vsub(msc_a); + if (vsub == NULL) + return -ENODEV; struct ran_msg msg = { .msg_type = RAN_MSG_COMMON_ID, .common_id = { .imsi = vsub->imsi, + .last_eutran_plmn_present = vsub->sgs.last_eutran_plmn_present, }, }; + if (vsub->sgs.last_eutran_plmn_present) { + memcpy(&msg.common_id.last_eutran_plmn, &vsub->sgs.last_eutran_plmn, + sizeof(vsub->sgs.last_eutran_plmn)); + } return msc_a_ran_down(msc_a, to_role, &msg); } static int msc_a_start_assignment(struct msc_a *msc_a, struct gsm_trans *cc_trans) { - struct call_leg *cl = msc_a->cc.call_leg; - struct msc_i *msc_i = msc_a_msc_i(msc_a); - struct gsm_network *net = msc_a_net(msc_a); + struct call_leg *cl; + bool cn_rtp_available; + bool ran_rtp_available; OSMO_ASSERT(!msc_a->cc.active_trans); msc_a->cc.active_trans = cc_trans; - OSMO_ASSERT(cc_trans && cc_trans->type == TRANS_CC); + cc_trans->cc.codecs.assignment = (struct sdp_audio_codec){}; - if (!cl) { - cl = msc_a->cc.call_leg = call_leg_alloc(msc_a->c.fi, - MSC_EV_CALL_LEG_TERM, - MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, - MSC_EV_CALL_LEG_RTP_COMPLETE); - OSMO_ASSERT(cl); - - /* HACK: We put the connection in loopback mode at the beginnig to - * trick the hNodeB into doing the IuUP negotiation with itself. - * This is a hack we need because osmo-mgw does not support IuUP yet, see OS#2459. */ - if (msc_a->c.ran->type == OSMO_RAT_UTRAN_IU) - cl->crcx_conn_mode[RTP_TO_RAN] = MGCP_CONN_LOOPBACK; - } + OSMO_ASSERT(cc_trans && cc_trans->type == TRANS_CC); + cl = msc_a_ensure_call_leg(msc_a, cc_trans); + if (!cl) + return -EINVAL; - if (net->use_osmux != OSMUX_USAGE_OFF) { - msc_i = msc_a_msc_i(msc_a); - if (msc_i->c.remote_to) { - /* TODO: investigate what to do in this case */ - LOG_MSC_A(msc_a, LOGL_ERROR, "Osmux not yet supported for inter-MSC"); - } else { - cl->ran_peer_supports_osmux = msc_i->ran_conn->ran_peer->remote_supports_osmux; - } + /* See if we can set a preliminary codec. If not, pass none for the time being. */ + trans_cc_filter_run(cc_trans); + + cn_rtp_available = call_leg_local_ip(cl, RTP_TO_CN); + ran_rtp_available = call_leg_local_ip(cl, RTP_TO_RAN); + + /* Set up RTP ports for both RAN and CN side. Even though we ask for both at the same time, the + * osmo_mgcpc_ep_fsm automagically waits for the first CRCX to complete before firing the second CRCX. The one + * issued first here will also be the first CRCX sent to the MGW. Usually both still need to be set up. */ + if (!cn_rtp_available) + call_leg_ensure_ci(cl, RTP_TO_CN, cc_trans->call_id, cc_trans, + &cc_trans->cc.local.audio_codecs, NULL); + if (!ran_rtp_available) { + struct sdp_audio_codecs *codecs; + if (msc_a->c.ran->force_mgw_codecs_to_ran.count) + codecs = &msc_a->c.ran->force_mgw_codecs_to_ran; + else + codecs = &cc_trans->cc.local.audio_codecs; + return call_leg_ensure_ci(cl, RTP_TO_RAN, cc_trans->call_id, cc_trans, codecs, NULL); } - /* This will lead to either MSC_EV_CALL_LEG_LOCAL_ADDR_AVAILABLE or MSC_EV_CALL_LEG_TERM. - * If the local address is already known, then immediately trigger. */ - if (call_leg_local_ip(cl, RTP_TO_RAN)) + /* Should these already be set up, immediately continue by retriggering the events signalling that the RTP + * ports are available. The ordering is: first CN, then RAN. */ + if (cn_rtp_available && ran_rtp_available) return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_RAN]); - else - return call_leg_ensure_ci(msc_a->cc.call_leg, RTP_TO_RAN, cc_trans->callref, cc_trans, NULL, NULL); + else if (cn_rtp_available) + return osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, cl->rtp[RTP_TO_CN]); + /* Otherwise wait for MGCP response and continue from there. */ + return 0; } int msc_a_try_call_assignment(struct gsm_trans *cc_trans) @@ -1684,8 +2132,14 @@ int msc_a_try_call_assignment(struct gsm_trans *cc_trans) return msc_a_start_assignment(msc_a, cc_trans); } -const char *msc_a_cm_service_type_to_use(enum osmo_cm_service_type cm_service_type) +/* Map CM Service type to use token. + * Given a CM Service type, return a matching token intended for osmo_use_count. + * For unknown service type, return NULL. + */ +const char *msc_a_cm_service_type_to_use(struct msc_a *msc_a, enum osmo_cm_service_type cm_service_type) { + struct gsm_network *net = msc_a_net(msc_a); + switch (cm_service_type) { case GSM48_CMSERV_MO_CALL_PACKET: case GSM48_CMSERV_EMERGENCY: @@ -1697,6 +2151,18 @@ const char *msc_a_cm_service_type_to_use(enum osmo_cm_service_type cm_service_ty case GSM48_CMSERV_SUP_SERV: return MSC_A_USE_CM_SERVICE_SS; + case GSM48_CMSERV_VGCS: + if (net->asci.enable) + return MSC_A_USE_CM_SERVICE_GCC; + else + return NULL; + + case GSM48_CMSERV_VBS: + if (net->asci.enable) + return MSC_A_USE_CM_SERVICE_BCC; + else + return NULL; + default: return NULL; } diff --git a/src/libmsc/msc_a_remote.c b/src/libmsc/msc_a_remote.c index 84eff0730..3b9693e59 100644 --- a/src/libmsc/msc_a_remote.c +++ b/src/libmsc/msc_a_remote.c @@ -1,6 +1,6 @@ /* The MSC-A role implementation variant that forwards requests to/from a remote MSC. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -179,8 +179,6 @@ static void msc_a_remote_send_handover_failure(struct msc_a *msc_a, enum gsm0808 return; msc_a_remote_msg_up_to_remote_msc(msc_a, MSC_ROLE_T, OSMO_GSUP_MSGT_E_PREPARE_HANDOVER_ERROR, &an_apdu); - msgb_free(an_apdu.msg); - return; } /* [MSC-A---------------------] [MSC-B---------------------] diff --git a/src/libmsc/msc_ho.c b/src/libmsc/msc_ho.c index 615b8cd70..54a959d2c 100644 --- a/src/libmsc/msc_ho.c +++ b/src/libmsc/msc_ho.c @@ -1,25 +1,21 @@ /* MSC Handover implementation */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <osmocom/core/fsm.h> @@ -43,6 +39,7 @@ #include <osmocom/msc/call_leg.h> #include <osmocom/msc/rtp_stream.h> #include <osmocom/msc/mncc_call.h> +#include <osmocom/msc/codec_mapping.h> struct osmo_fsm msc_ho_fsm; @@ -62,14 +59,17 @@ static const struct osmo_tdef_state_timeout msc_ho_fsm_timeouts[32] = { static __attribute__((constructor)) void msc_ho_fsm_init() { - osmo_fsm_register(&msc_ho_fsm); + OSMO_ASSERT(osmo_fsm_register(&msc_ho_fsm) == 0); } void msc_ho_down_required_reject(struct msc_a *msc_a, enum gsm0808_cause cause) { - struct msc_i *msc_i = msc_a_msc_i(msc_a); + struct msc_i *msc_i; uint32_t event; + msc_i = msc_a_msc_i(msc_a); + OSMO_ASSERT(msc_i); + struct ran_msg ran_enc_msg = { .msg_type = RAN_MSG_HANDOVER_REQUIRED_REJECT, .handover_required_reject = { @@ -380,6 +380,8 @@ static void msc_ho_send_handover_request(struct msc_a *msc_a) struct vlr_subscr *vsub = msc_a_vsub(msc_a); struct gsm_network *net = msc_a_net(msc_a); struct gsm0808_channel_type channel_type; + struct gsm0808_speech_codec_list scl = {}; + struct gsm_trans *cc_trans = msc_a->cc.active_trans; struct ran_msg ran_enc_msg = { .msg_type = RAN_MSG_HANDOVER_REQUEST, .handover_request = { @@ -402,13 +404,57 @@ static void msc_ho_send_handover_request(struct msc_a *msc_a) }, }; - if (msc_a->cc.active_trans) { - if (mncc_bearer_cap_to_channel_type(&channel_type, &msc_a->cc.active_trans->bearer_cap)) { - msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, - "Failed to encode Bearer Cap to Channel Type\n"); + if (msc_a->geran_encr.key_len) + LOG_MSC_A(msc_a, LOGL_DEBUG, "HO Request with ciphering: A5/%d kc %s kc128 %s\n", + msc_a->geran_encr.alg_id - 1, + osmo_hexdump_nospc_c(OTC_SELECT, msc_a->geran_encr.key, msc_a->geran_encr.key_len), + msc_a->geran_encr.kc128_present ? + osmo_hexdump_nospc_c(OTC_SELECT, msc_a->geran_encr.kc128, sizeof(msc_a->geran_encr.kc128)) + : "-"); + + if (cc_trans) { + switch (cc_trans->bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + if (sdp_audio_codecs_to_gsm0808_channel_type(&channel_type, + &cc_trans->cc.local.audio_codecs)) { + msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Failed to determine Channel Type for Handover Request message (speech)\n"); + return; + } + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + if (csd_bs_list_to_gsm0808_channel_type(&channel_type, &cc_trans->cc.local.bearer_services)) { + msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Failed to determine Channel Type for Handover Request message (CSD)\n"); + return; + } + break; + default: + msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "Failed to create" + " Handover Request message for information transfer capability %d\n", + cc_trans->bearer_cap.transfer); return; } + ran_enc_msg.handover_request.geran.channel_type = &channel_type; + ran_enc_msg.handover_request.call_id_present = true; + ran_enc_msg.handover_request.call_id = cc_trans->call_id; + + /* Call assignment is now capable of re-assigning to overcome a codec mismatch with the remote call leg. + * But for inter-MSC handover, that is not supported yet. So keep here the old limitation of only + * offering the assigned codec. */ + if (sdp_audio_codec_is_set(&cc_trans->cc.codecs.assignment)) + sdp_audio_codec_to_speech_codec_list(&scl, &cc_trans->cc.codecs.assignment); + else + sdp_audio_codecs_to_speech_codec_list(&scl, &cc_trans->cc.local.audio_codecs); + if (!scl.len) { + msc_ho_failed(msc_a, GSM0808_CAUSE_EQUIPMENT_FAILURE, "Failed to compose" + " Codec List (MSC Preferred) for Handover Request message\n"); + return; + } + ran_enc_msg.handover_request.codec_list_msc_preferred = &scl; } gsm0808_cell_id_from_cgi(&ran_enc_msg.handover_request.cell_id_serving, CELL_IDENT_WHOLE_GLOBAL, &vsub->cgi); @@ -560,7 +606,7 @@ static int msc_ho_start_inter_msc_call_forwarding(struct msc_a *msc_a, struct ms /* Backup old cell's RTP IP:port and codec data */ msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote; - msc_a->ho.old_cell.codec = rtp_to_ran->codec; + msc_a->ho.old_cell.codecs = rtp_to_ran->codecs; /* Blindly taken over from an MNCC trace of existing code: send an all-zero CCCAP: */ outgoing_call_req.fields |= MNCC_F_CCCAP; @@ -661,7 +707,7 @@ static void msc_ho_rx_request_ack(struct msc_a *msc_a, struct msc_a_ran_dec_data } msc_a->ho.new_cell.ran_remote_rtp = hra->ran_dec->handover_request_ack.remote_rtp; - if (osmo_sockaddr_str_is_set(&msc_a->ho.new_cell.ran_remote_rtp)) { + if (osmo_sockaddr_str_is_nonzero(&msc_a->ho.new_cell.ran_remote_rtp)) { LOG_HO(msc_a, LOGL_DEBUG, "Request Ack contains cell's RTP address " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.new_cell.ran_remote_rtp)); } @@ -670,7 +716,7 @@ static void msc_ho_rx_request_ack(struct msc_a *msc_a, struct msc_a_ran_dec_data msc_a->ho.new_cell.codec = hra->ran_dec->handover_request_ack.codec; if (hra->ran_dec->handover_request_ack.codec_present) { LOG_HO(msc_a, LOGL_DEBUG, "Request Ack contains codec %s\n", - osmo_mgcpc_codec_name(msc_a->ho.new_cell.codec)); + gsm0808_speech_codec_type_name(msc_a->ho.new_cell.codec.type)); } } @@ -684,7 +730,7 @@ static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a) return; } - if (!osmo_sockaddr_str_is_set(&msc_a->ho.new_cell.ran_remote_rtp)) { + if (!osmo_sockaddr_str_is_nonzero(&msc_a->ho.new_cell.ran_remote_rtp)) { LOG_HO(msc_a, LOGL_DEBUG, "New cell's RTP IP:port not yet known, not switching RTP stream\n"); return; } @@ -697,7 +743,7 @@ static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a) /* Backup old cell's RTP IP:port and codec data */ msc_a->ho.old_cell.ran_remote_rtp = rtp_to_ran->remote; - msc_a->ho.old_cell.codec = rtp_to_ran->codec; + msc_a->ho.old_cell.codecs = rtp_to_ran->codecs; LOG_HO(msc_a, LOGL_DEBUG, "Switching RTP stream to new cell: from " OSMO_SOCKADDR_STR_FMT " to " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(&msc_a->ho.old_cell.ran_remote_rtp), @@ -716,10 +762,18 @@ static void msc_ho_rtp_switch_to_new_cell(struct msc_a *msc_a) /* Switch over to the new peer */ rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.new_cell.ran_remote_rtp); - if (msc_a->ho.new_cell.codec_present) - rtp_stream_set_codec(rtp_to_ran, msc_a->ho.new_cell.codec); - else + if (msc_a->ho.new_cell.codec_present) { + const struct codec_mapping *m; + m = codec_mapping_by_gsm0808_speech_codec_type(msc_a->ho.new_cell.codec.type); + /* TODO: use codec_mapping_by_gsm0808_speech_codec() to also match on codec.cfg */ + if (!m) + LOG_HO(msc_a, LOGL_ERROR, "Cannot resolve codec: %s\n", + gsm0808_speech_codec_type_name(msc_a->ho.new_cell.codec.type)); + else + rtp_stream_set_one_codec(rtp_to_ran, &m->sdp); + } else { LOG_HO(msc_a, LOGL_ERROR, "No codec is set\n"); + } rtp_stream_commit(rtp_to_ran); } @@ -738,7 +792,7 @@ static void msc_ho_rtp_rollback_to_old_cell(struct msc_a *msc_a) return; } - if (!osmo_sockaddr_str_is_set(&msc_a->ho.old_cell.ran_remote_rtp)) { + if (!osmo_sockaddr_str_is_nonzero(&msc_a->ho.old_cell.ran_remote_rtp)) { LOG_HO(msc_a, LOGL_DEBUG, "Have no RTP IP:port for the old cell, not switching back to\n"); return; } @@ -758,7 +812,7 @@ static void msc_ho_rtp_rollback_to_old_cell(struct msc_a *msc_a) /* Switch back to the old cell */ rtp_stream_set_remote_addr(rtp_to_ran, &msc_a->ho.old_cell.ran_remote_rtp); - rtp_stream_set_codec(rtp_to_ran, msc_a->ho.old_cell.codec); + rtp_stream_set_codecs(rtp_to_ran, &msc_a->ho.old_cell.codecs); rtp_stream_commit(rtp_to_ran); } diff --git a/src/libmsc/msc_i.c b/src/libmsc/msc_i.c index f7aab0db1..d3616f3ae 100644 --- a/src/libmsc/msc_i.c +++ b/src/libmsc/msc_i.c @@ -1,6 +1,6 @@ /* Code to manage a subscriber's MSC-I role */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ diff --git a/src/libmsc/msc_i_remote.c b/src/libmsc/msc_i_remote.c index 7b9598423..c5d22a2e3 100644 --- a/src/libmsc/msc_i_remote.c +++ b/src/libmsc/msc_i_remote.c @@ -1,6 +1,6 @@ /* The MSC-I role implementation variant that forwards requests to/from a remote MSC. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ diff --git a/src/libmsc/msc_net_init.c b/src/libmsc/msc_net_init.c index 91b6165bd..1fc712aff 100644 --- a/src/libmsc/msc_net_init.c +++ b/src/libmsc/msc_net_init.c @@ -24,6 +24,7 @@ #include "config.h" #include <osmocom/core/tdef.h> +#include <osmocom/crypt/utran_cipher.h> #include <osmocom/msc/gsm_data.h> #include <osmocom/msc/vlr.h> @@ -31,10 +32,26 @@ #include <osmocom/msc/gsm_04_11_gsup.h> #include <osmocom/msc/gsm_09_11.h> +/* TODO: would be great to have all timer declarations in one place */ +#include <osmocom/msc/ran_infra.h> +#include <osmocom/msc/sccp_ran.h> +#include <osmocom/msc/call_leg.h> + struct osmo_tdef mncc_tdefs[] = { {} }; +struct osmo_tdef_group msc_tdef_group[] = { + { .name = "vlr", .tdefs = msc_tdefs_vlr, .desc = "VLR (Visitors Location Register)" }, + { .name = "mgw", .tdefs = g_mgw_tdefs, .desc = "MGW (Media Gateway) interface" }, + { .name = "mncc", .tdefs = mncc_tdefs, .desc = "MNCC (Mobile Network Call Control) interface" }, + { .name = "sccp", .tdefs = g_sccp_tdefs, .desc = "SCCP (Signalling Connection Control Part)" }, + { .name = "geran", .tdefs = msc_tdefs_geran, .desc = "GERAN (GSM EDGE Radio Access Network)" }, + { .name = "utran", .tdefs = msc_tdefs_utran, .desc = "UTRAN (UMTS Terrestrial Radio Access Network)" }, + { .name = "sgs", .tdefs = msc_tdefs_sgs, .desc = "SGs interface towards MME" }, + { /* terminator */ } +}; + #include <osmocom/core/stat_item.h> struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv) @@ -49,19 +66,16 @@ struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv) /* Permit a compile-time default of A5/3 and A5/1 */ net->a5_encryption_mask = (1 << 3) | (1 << 1); - net->uea_encryption = true; - - /* Use 30 min periodic update interval as sane default */ - net->t3212 = 5; + /* Permit a compile-time default of UEA2 and UEA1 */ + net->uea_encryption_mask = (1 << OSMO_UTRAN_UEA2) | (1 << OSMO_UTRAN_UEA1); net->mncc_guard_timeout = 180; net->ncss_guard_timeout = 30; - net->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT; - INIT_LLIST_HEAD(&net->trans_list); INIT_LLIST_HEAD(&net->upqueue); INIT_LLIST_HEAD(&net->neighbor_ident_list); + INIT_LLIST_HEAD(&net->asci.gcr_lists); /* init statistics */ net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0); @@ -116,9 +130,15 @@ int msc_gsup_client_start(struct gsm_network *net) net->gcm = gsup_client_mux_alloc(net); OSMO_ASSERT(net->gcm); + /* If no IPA name is configured, we need to provide a default + * right here, in order for the defaulted name to get inserted + * as source_name in GSUP response messages. */ + if (!net->msc_ipa_name) + net->msc_ipa_name = "unnamed-MSC"; + ipa_dev = talloc_zero(net->gcm, struct ipaccess_unit); ipa_dev->unit_name = "MSC"; - ipa_dev->serno = net->msc_ipa_name; /* NULL unless configured via VTY */ + ipa_dev->serno = net->msc_ipa_name; ipa_dev->swversion = PACKAGE_NAME "-" PACKAGE_VERSION; *net->gcm = (struct gsup_client_mux){ diff --git a/src/libmsc/msc_t.c b/src/libmsc/msc_t.c index 6b96c26ca..eb6c79784 100644 --- a/src/libmsc/msc_t.c +++ b/src/libmsc/msc_t.c @@ -1,6 +1,6 @@ /* The MSC-T role, a transitional RAN connection during Handover. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -38,6 +38,7 @@ #include <osmocom/msc/vlr.h> #include <osmocom/msc/msc_i.h> #include <osmocom/msc/gsm_data.h> +#include <osmocom/msc/codec_mapping.h> static struct osmo_fsm msc_t_fsm; @@ -145,7 +146,6 @@ static void msc_t_send_handover_failure(struct msc_t *msc_t, enum gsm0808_cause return; msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_FAILURE, &an_apdu); - msgb_free(an_apdu.msg); } static int msc_t_ho_request_decode_and_store_cb(struct osmo_fsm_inst *msc_t_fi, void *data, @@ -160,7 +160,7 @@ static int msc_t_ho_request_decode_and_store_cb(struct osmo_fsm_inst *msc_t_fi, } msc_t->inter_msc.cell_id_target = ran_dec->handover_request.cell_id_target; - msc_t->inter_msc.callref = ran_dec->handover_request.call_id; + msc_t->inter_msc.call_id = ran_dec->handover_request.call_id; /* TODO other parameters...? * Global Call Reference @@ -238,7 +238,6 @@ static int msc_t_find_ran_peer_from_ho_request(struct msc_t *msc_t) static int msc_t_send_stored_ho_request__decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data, const struct ran_msg *ran_dec) { - int rc; struct an_apdu an_apdu; struct msc_t *msc_t = msc_t_priv(msc_t_fi); struct osmo_sockaddr_str *rtp_ran_local = data; @@ -263,9 +262,7 @@ static int msc_t_send_stored_ho_request__decode_cb(struct osmo_fsm_inst *msc_t_f }; if (!an_apdu.msg) return -EIO; - rc = msc_t_down_l2_co(msc_t, &an_apdu, true); - msgb_free(an_apdu.msg); - return rc; + return msc_t_down_l2_co(msc_t, &an_apdu, true); } /* The MGW endpoint is created, we know our AoIP Transport Layer Address and can send the Handover Request to the RAN @@ -361,8 +358,8 @@ void msc_t_fsm_wait_local_rtp_onenter(struct osmo_fsm_inst *fi, uint32_t prev_st MSC_EV_CALL_LEG_RTP_LOCAL_ADDR_AVAILABLE, MSC_EV_CALL_LEG_RTP_COMPLETE); if (!msc_t->inter_msc.call_leg - || call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_RAN, msc_t->inter_msc.callref, NULL, NULL, NULL) - || call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_CN, msc_t->inter_msc.callref, NULL, NULL, NULL)) { + || call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_RAN, msc_t->inter_msc.call_id, NULL, NULL, NULL) + || call_leg_ensure_ci(msc_t->inter_msc.call_leg, RTP_TO_CN, msc_t->inter_msc.call_id, NULL, NULL, NULL)) { msc_t_error("Failed to set up call leg\n"); return; } @@ -444,7 +441,7 @@ static int msc_t_patch_and_send_ho_request_ack(struct msc_t *msc_t, const struct /* Also need to fetch the RTP IP:port from AoIP Transport Address IE to tell the MGW about it */ if (rtp_ran) { - if (osmo_sockaddr_str_is_set(&r->remote_rtp)) { + if (osmo_sockaddr_str_is_nonzero(&r->remote_rtp)) { LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(&r->remote_rtp)); rtp_stream_set_remote_addr(rtp_ran, &r->remote_rtp); @@ -452,11 +449,20 @@ static int msc_t_patch_and_send_ho_request_ack(struct msc_t *msc_t, const struct LOG_MSC_T(msc_t, LOGL_DEBUG, "No RTP IP:port in Handover Request Ack\n"); } if (r->codec_present) { - LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got %s\n", - osmo_mgcpc_codec_name(r->codec)); - rtp_stream_set_codec(rtp_ran, r->codec); - if (rtp_cn) - rtp_stream_set_codec(rtp_cn, r->codec); + const struct codec_mapping *m = codec_mapping_by_gsm0808_speech_codec_type(r->codec.type); + /* TODO: use codec_mapping_by_gsm0808_speech_codec() to also match on codec.cfg */ + if (!m) { + LOG_MSC_T(msc_t, LOGL_ERROR, "Cannot resolve codec in Handover Request Ack: %s / %s\n", + gsm0808_speech_codec_type_name(r->codec.type), + m ? sdp_audio_codec_to_str(&m->sdp) : "(unknown)"); + } else { + LOG_MSC_T(msc_t, LOGL_DEBUG, "From Handover Request Ack, got codec %s / %s\n", + gsm0808_speech_codec_type_name(r->codec.type), + sdp_audio_codec_to_str(&m->sdp)); + rtp_stream_set_one_codec(rtp_ran, &m->sdp); + if (rtp_cn) + rtp_stream_set_one_codec(rtp_cn, &m->sdp); + } } else { LOG_MSC_T(msc_t, LOGL_DEBUG, "No codec in Handover Request Ack\n"); } @@ -472,9 +478,7 @@ static int msc_t_patch_and_send_ho_request_ack(struct msc_t *msc_t, const struct if (!an_apdu.msg) return -EIO; /* Send to remote MSC via msc_a_remote role */ - rc = msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE, &an_apdu); - msgb_free(an_apdu.msg); - return rc; + return msub_role_dispatch(msc_t->c.msub, MSC_ROLE_A, MSC_A_EV_FROM_T_PREPARE_HANDOVER_RESPONSE, &an_apdu); } static int msc_t_wait_ho_request_ack_decode_cb(struct osmo_fsm_inst *msc_t_fi, void *data, diff --git a/src/libmsc/msc_t_remote.c b/src/libmsc/msc_t_remote.c index 22c4e22fd..aea763337 100644 --- a/src/libmsc/msc_t_remote.c +++ b/src/libmsc/msc_t_remote.c @@ -1,6 +1,6 @@ /* The MSC-T role implementation variant that forwards requests to/from a remote MSC. */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ diff --git a/src/libmsc/msc_vgcs.c b/src/libmsc/msc_vgcs.c new file mode 100644 index 000000000..264fa4953 --- /dev/null +++ b/src/libmsc/msc_vgcs.c @@ -0,0 +1,2765 @@ +/* Handle VGCS/VBCS calls. (Voice Group/Broadcast Call Service). */ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Andreas Eversberg + * + * 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/>. + */ + +/* The process consists of four state machines: + * + * The call control state machine "GCC" handles the voice group/broadcast call. + * There is one instance for every call. It is mainly controlled by the calling + * subscriber. The state machine is described in 3GPP TS 44.068 / 44.069. + * One SCCP connection to the calling subscriber is associated with the state + * machine. Once the calling subscriber leaves or is assigned to the VGCS/VBS + * channel, the association to the MSC-A role is removed and the SCCP connection + * is closed. The state machine with the transaction still exists until the end + * of the call. + * + * The BSS control state machine "vgcs_bss_fsm" handles the call in each BSC. + * There are as many instances as there are BSCs where the call is placed to. + * The instances are linked to the call control in a 1:n relation. + * One SCCP connection for every BSC is associated with the state machine. + * It sets up the call in the BSC and handles the uplink control and signaling + * with the talking phone. + * + * The resource controling state machine "vgcs_cell_fsm" handles the channel for + * each BTS that has a VGCS for the call. The instances are linked to the BSS + * control in a 1:n relation. + * One SCCP connection for every cell is associated with each list entry. + * It assigns the VGCS/VBS channel and the conference bridge in the MGW. + * + * The MGW endpoint state machine "vgcs_mgw_ep_fsm" handles the endpoint + * connection for each call. It controls the clearing of the MGW connections + * in case of endpoint failure. All instances of the resource controlling state + * machine are linked to this state machine in a 1:n relation. + * + * Setup of a call: + * + * When the calling subscriber dials a group/broadcast call, the GCR is checked + * for an existing Group ID. If it exists, the call is setup towards the a given + * list of MSCs for this Group ID. Also the channels are assigned for a given + * list of cells for this Group ID. + * The call can also be initiated via VTY. + * + * Then the calling subscriber is assigned to the VGCS channel of the same cell + * where the call was initialized. Afterwards the call is connected. The calling + * subscriber may then stay on the uplink or release it. + * + * Uplink control: + * + * Any BSC may indicate a talking subscriber. If there is no talking subscriber + * yet, the uplink is granted, otherwise it is rejected. If the uplink is in + * use on one BSC, all other BSCs will be blocked. If the uplink becomes free, + * all other BSCs will be unblocked. + * + * Termination of the call: + * + * The calling subscriber accesses the uplink. The it sends a termination + * request. This request is acknowledged by a termination command towards + * the calling subscriber. The call is cleared. + * The call can also be terminated via VTY and/or a timeout. + * + */ + +#include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> +#include <osmocom/gsm/protocol/gsm_44_068.h> +#include <osmocom/sigtran/sccp_helpers.h> +#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h> + +#include <osmocom/msc/gsm_data.h> +#include <osmocom/msc/sccp_ran.h> +#include <osmocom/msc/ran_infra.h> +#include <osmocom/msc/ran_peer.h> +#include <osmocom/msc/ran_msg_a.h> +#include <osmocom/msc/msub.h> +#include <osmocom/msc/debug.h> +#include <osmocom/msc/msc_a.h> +#include <osmocom/msc/vlr.h> +#include <osmocom/msc/rtp_stream.h> +#include <osmocom/msc/codec_mapping.h> +#include <osmocom/msc/msc_vgcs.h> +#include <osmocom/msc/asci_gcr.h> + +#define S(x) (1 << (x)) + +#define LOG_GCC(trans, level, fmt, args...) \ + LOGP((trans) ? ((trans->type == TRANS_GCC) ? DGCC : DBCC) : DASCI, level, \ + (trans) ? ((trans->type == TRANS_GCC) ? ("GCC callref %s: " fmt) : ("BCC callref %s: " fmt)) : "%s" fmt, \ + (trans) ? gsm44068_group_id_string(trans->callref) : "", ##args) +#define LOG_BSS(bss, level, fmt, args...) \ + LOGP(DASCI, level, \ + (bss->trans_type == TRANS_GCC) ? ("GCC callref %s, BSS #%s: " fmt) : ("BCC callref %s, BSS #%s: " fmt), \ + gsm44068_group_id_string(bss->callref), osmo_ss7_pointcode_print(NULL, bss->pc), ##args) +#define LOG_CELL(cell, level, fmt, args...) \ + LOGP(DASCI, level, \ + (cell->trans_type == TRANS_GCC) ? ("GCC callref %s, BSS #%s, CID %d: " fmt) \ + : ("BCC callref %s, BSS #%s, CID %d: " fmt), \ + gsm44068_group_id_string(cell->callref), osmo_ss7_pointcode_print(NULL, cell->pc), cell->cell_id, ##args) + +static struct osmo_fsm vgcs_bcc_fsm; +static struct osmo_fsm vgcs_gcc_fsm; +static struct osmo_fsm vgcs_bss_fsm; +static struct osmo_fsm vgcs_cell_fsm; +static struct osmo_fsm vgcs_mgw_ep_fsm; + +static __attribute__((constructor)) void vgcs_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&vgcs_bcc_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_gcc_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_bss_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_cell_fsm) == 0); + OSMO_ASSERT(osmo_fsm_register(&vgcs_mgw_ep_fsm) == 0); +} + +const char *gsm44068_group_id_string(uint32_t callref) +{ + static char string[9]; + + snprintf(string, sizeof(string), "%08u", callref); + string[sizeof(string) - 1] = '\0'; + + return string; +} + +/* Resolve ran peer from point-code */ +static struct ran_peer *ran_peer_for_pc(struct gsm_network *msc_network, int pc) +{ + struct sccp_ran_inst *sri; + struct osmo_sccp_addr addr = {}; + struct ran_peer *rp; + + sri = msc_network->a.sri; + if (!osmo_sccp_get_ss7(sri->sccp)) { + LOGP(DASCI, LOGL_ERROR, "No SS7???\n"); + return NULL; + } + osmo_sccp_make_addr_pc_ssn(&addr, pc, sri->ran->ssn); + rp = ran_peer_find_by_addr(sri, &addr); + + return rp; +} + +/* Encode message and send towards BSC. */ +int ran_encode_and_send(struct osmo_fsm_inst *fi, struct ran_msg *ran_msg, struct ran_conn *conn, bool initial) +{ + struct msgb *l3_msg; + int rc; + + l3_msg = ran_a_encode(fi, ran_msg); + if (!l3_msg) { + LOGP(DASCI, LOGL_ERROR, "ran_a_encode() failed.\n"); + return -EINVAL; + } + rc = ran_conn_down_l2_co(conn, l3_msg, initial); + msgb_free(l3_msg); + + return rc; +} + +/* Transmit DTAP message to talker + * This is used for sending group/broadcast call control messages. */ +int tx_dtap_to_talker(struct vgcs_bss *bss, struct msgb *l3_msg) +{ + struct ran_msg ran_msg; + struct gsm48_hdr *gh = msgb_l3(l3_msg) ? : l3_msg->data; + uint8_t pdisc = gsm48_hdr_pdisc(gh); + int rc; + + + LOG_BSS(bss, LOGL_DEBUG, "Sending DTAP: %s %s\n", + gsm48_pdisc_name(pdisc), gsm48_pdisc_msgtype_name(pdisc, gsm48_hdr_msg_type(gh))); + + ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_DTAP, + .dtap = l3_msg, + }; + + rc = ran_encode_and_send(bss->fi, &ran_msg, bss->conn, false); + + return rc; +} + +/* + * GCC/BCC Message transcoding + */ + +static void _add_cause_ie(struct msgb *msg, uint8_t cause, uint8_t *diag, uint8_t diag_len) +{ + uint8_t *ie = msgb_put(msg, 2 + diag_len); + + ie[0] = 1 + diag_len; + ie[1] = cause; + if (diag && diag_len) { + ie[1] |= 0x80; + memcpy(ie + 2, diag, diag_len); + } +} + +static void _add_callref_ie(struct msgb *msg, uint32_t callref, bool with_prio, uint8_t prio) +{ + uint32_t ie; + + ie = callref << 5; + if (with_prio) + ie |= 0x10 | (prio << 1); + msgb_put_u32(msg, ie); +} + +static int _msg_too_short(void) +{ + LOGP(DASCI, LOGL_ERROR, "MSG too short.\n"); + return -EINVAL; +} + +static int _ie_invalid(void) +{ + LOGP(DASCI, LOGL_ERROR, "IE invalid.\n"); + return -EINVAL; +} + +static int _rx_callref(uint8_t *ie, unsigned int remaining_len, uint32_t *callref, bool *with_prio, uint8_t *prio) +{ + uint8_t ie_len; + + ie_len = sizeof(uint32_t); + if (remaining_len < ie_len) + return _msg_too_short(); + *callref = osmo_load32be(ie) >> 5; + if (ie[3] & 0x10) { + *with_prio = true; + *prio = (ie[3] >> 1) & 0x7; + } else + *with_prio = false; + + return ie_len; +} + +/* 3GPP TS 44.068 Clause 8.1 */ +static int gsm44068_tx_connect(struct gsm_trans *trans, uint8_t pdisc, uint32_t callref, bool with_prio, uint8_t prio, + uint8_t oi, uint8_t talker_prio, bool with_sms, uint8_t sms_dc, uint8_t sms_gp) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX CONNECT"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + uint8_t ie; + + gh->proto_discr = pdisc; + gh->msg_type = OSMO_GSM44068_MSGT_CONNECT; + _add_callref_ie(msg, callref, with_prio, prio); + ie = (talker_prio << 4) | oi; + msgb_put_u8(msg, ie); + if (with_sms) { + ie = OSMO_GSM44068_IEI_SMS_INDICATIONS | (sms_dc << 1) | sms_gp; + msgb_put_u8(msg, ie); + } + + /* Send to calling subscriber, depending on the link he is. */ + if (trans->msc_a) + return msc_a_tx_dtap_to_i(trans->msc_a, msg); + if (trans->gcc.uplink_bss) + return tx_dtap_to_talker(trans->gcc.uplink_bss, msg); + msgb_free(msg); + return -EIO; +} + +/* The Get Status procedure is not used by the current implementation. + * It is commented out, so it can be used in the future. + * The idea is to have a complete set of GCC/BCC message transcoding. + */ +#if 0 +/* 3GPP TS 44.068 Clause 8.2 */ +static int gsm44068_tx_get_status(struct gsm_trans *trans, uint8_t pdisc, struct osmo_mobile_identity *mi) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX GET STATUS"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->proto_discr = pdisc; + gh->msg_type = OSMO_GSM44068_MSGT_GET_STATUS; + if (mi) { + uint8_t *l; + int rc; + + l = msgb_tl_put(msg, OSMO_GSM44068_IEI_MOBILE_IDENTITY); + rc = osmo_mobile_identity_encode_msgb(msg, mi, false); + if (rc < 0) { + msgb_free(msg); + return -EINVAL; + } + *l = rc; + } + + /* Send to calling subscriber, depending on the link he is. */ + if (trans->msc_a) + return msc_a_tx_dtap_to_i(trans->msc_a, msg); + if (trans->gcc.uplink_bss) + return tx_dtap_to_talker(trans->gcc.uplink_bss, msg); + msgb_free(msg); + return -EIO; +} +#endif + +/* 3GPP TS 44.068 Clause 8.3 and 8.3a */ +static int gsm44068_rx_immediate_setup(struct msgb *msg, uint8_t *talker_prio, uint8_t *key_seq, + struct gsm48_classmark2 *cm2, struct osmo_mobile_identity *mi, + uint32_t *callref, bool *with_prio, uint8_t *prio, char *user_user) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + uint8_t ie_len; + uint64_t otdi; + int i; + int rc; + + /* Talker priority / Cyphering key sequence */ + if (remaining_len < 1) + return _msg_too_short(); + *talker_prio = ie[0] & 0x07; + *key_seq = (ie[0] >> 4) & 0x07; + remaining_len -= 1; + ie += 1; + + /* Mobile station classmark 2 */ + if (remaining_len < 4) + return _msg_too_short(); + ie_len = ie[0]; + if (remaining_len < ie_len + 1) + return _msg_too_short(); + if (ie_len != 3) + return _ie_invalid(); + memcpy(cm2, ie + 1, ie_len); + remaining_len -= ie_len + 1; + ie += ie_len + 1; + + /* Mobile indentity */ + if (gh->msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP) { + /* IMMEDIATE SETUP uses IMSI/TMSI */ + if (remaining_len < 2) + return _msg_too_short(); + ie_len = ie[0]; + if (remaining_len < ie_len + 1) + return _msg_too_short(); + rc = osmo_mobile_identity_decode(mi, ie + 1, ie_len, false); + if (rc) { + LOGP(DMM, LOGL_ERROR, "Failure to decode Mobile Identity in GCC/BCC IMMEDDIATE SETUP" + " (rc=%d)\n", rc); + return -EINVAL; + } + remaining_len -= ie_len + 1; + ie += ie_len + 1; + } else { + /* IMMEDIATE SETUP 2 uses TMSI only */ + if (remaining_len < 4) + return _msg_too_short(); + mi->type = GSM_MI_TYPE_TMSI; + mi->tmsi = osmo_load32be(ie); + remaining_len -= 4; + ie += 4; + } + + /* Call reference */ + rc = _rx_callref(ie, remaining_len, callref, with_prio, prio); + if (rc < 0) + return rc; + remaining_len -= rc; + ie += rc; + + /* OTID */ + if (gh->msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2 && user_user) { + ie_len = 5; + if (remaining_len < ie_len) + return _msg_too_short(); + otdi = osmo_load32be(ie + 1) | ((uint64_t)ie[0] << 32); + + for (i = 0; i < 12; i++) { + user_user[i] = (otdi % 10) + '0'; + otdi /= 10; + } + user_user[i] = '\0'; + remaining_len -= ie_len; + ie += ie_len; + } else if (user_user) + user_user[0] = '\0'; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.4 */ +static int gsm44068_tx_set_parameter(struct gsm_trans *trans, uint8_t pdisc, uint8_t da, uint8_t ua, uint8_t comm, + uint8_t oi) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX SET PARAMETER"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + uint8_t ie; + + gh->proto_discr = pdisc; + gh->msg_type = OSMO_GSM44068_MSGT_SET_PARAMETER; + ie = (da << 3) | (ua << 2) | (comm << 1) | oi; + msgb_put_u8(msg, ie); + + /* Send to calling subscriber, depending on the link he is. */ + if (trans->msc_a) + return msc_a_tx_dtap_to_i(trans->msc_a, msg); + if (trans->gcc.uplink_bss) + return tx_dtap_to_talker(trans->gcc.uplink_bss, msg); + msgb_free(msg); + return -EIO; +} + +/* 3GPP TS 44.068 Clause 8.5 */ +static int gsm44068_rx_setup(struct msgb *msg, bool *with_talker_prio, uint8_t *talker_prio, + uint32_t *callref, bool *with_prio, uint8_t *prio, char *user_user) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + struct tlv_parsed tp; + struct tlv_p_entry *tlv; + int rc; + + /* Call reference */ + rc = _rx_callref(ie, remaining_len, callref, with_prio, prio); + if (rc < 0) + return rc; + remaining_len -= rc; + ie += rc; + + rc = tlv_parse(&tp, &osmo_gsm44068_att_tlvdef, ie, remaining_len, 0, 0); + if (rc < 0) + return _ie_invalid(); + + /* User-user */ + tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_USER_USER); + if (tlv && tlv->len && tlv->len <= 1 + 12 && user_user) { + memcpy(user_user, tlv->val, tlv->len - 1); + user_user[tlv->len - 1] = '\0'; + } + + /* Talker priority */ + tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_TALKER_PRIORITY); + if (tlv && tlv->len) { + *with_talker_prio = true; + *talker_prio = tlv->val[0] & 0x07; + } else + *with_talker_prio = false; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.6 */ +static int gsm44068_rx_status(struct msgb *msg, uint8_t *cause, uint8_t *diag, uint8_t *diag_len, + bool *with_call_state, enum osmo_gsm44068_call_state *call_state, + bool *with_state_attrs, uint8_t *da, uint8_t *ua, uint8_t *comm, uint8_t *oi) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + uint8_t ie_len; + struct tlv_parsed tp; + struct tlv_p_entry *tlv; + int rc; + + /* Cause */ + if (remaining_len < 2 || ie[0] < remaining_len - 2) + return _msg_too_short(); + ie_len = ie[0]; + if (remaining_len < ie_len + 1) + return _msg_too_short(); + if (ie_len < 1) + return _ie_invalid(); + *cause = ie[1] & 0x7f; + *diag_len = ie_len - 1; + if (*diag_len) + memcpy(diag, ie + 2, ie_len - 1); + remaining_len -= ie_len + 1; + ie += ie_len + 1; + + rc = tlv_parse(&tp, &osmo_gsm44068_att_tlvdef, ie, remaining_len, 0, 0); + if (rc < 0) + return _ie_invalid(); + + /* Call state */ + tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_CALL_STATE); + if (tlv) { + *with_call_state = true; + *call_state = tlv->val[0] & 0x7; + } else + *with_call_state = false; + + /* State attributes */ + tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_STATE_ATTRIBUTES); + if (tlv) { + *with_state_attrs = true; + *da = (tlv->val[0] >> 3) & 0x1; + *ua = (tlv->val[0] >> 2) & 0x1; + *comm = (tlv->val[0] >> 1) & 0x1; + *oi = tlv->val[0] & 0x1; + } else + *with_state_attrs = false; + + return 0; +} + +/* 3GPP TS 44.068 Clause 8.7 and 8.8 */ +static int gsm44068_tx_termination(struct msc_a *msc_a, struct vgcs_bss *bss, uint8_t pdisc, uint8_t msg_type, + uint8_t cause, uint8_t *diag, uint8_t diag_len) +{ + struct msgb *msg = gsm44068_msgb_alloc_name("GSM 44.068 TX TERMINATION"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + gh->proto_discr = pdisc; + gh->msg_type = msg_type; + _add_cause_ie(msg, cause, diag, diag_len); + + /* Send to calling subscriber, depending on the link he is. */ + if (msc_a) + return msc_a_tx_dtap_to_i(msc_a, msg); + if (bss) + return tx_dtap_to_talker(bss, msg); + msgb_free(msg); + return -EIO; +} + +/* 3GPP TS 44.068 Clause 8.9 */ +static int gsm44068_rx_termination_req(struct msgb *msg, uint32_t *callref, bool *with_prio, uint8_t *prio, + bool *with_talker_prio, uint8_t *talker_prio) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int remaining_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t *ie = gh->data; + struct tlv_parsed tp; + struct tlv_p_entry *tlv; + int rc; + + /* Call reference */ + rc = _rx_callref(ie, remaining_len, callref, with_prio, prio); + if (rc < 0) + return rc; + remaining_len -= rc; + ie += rc; + + rc = tlv_parse(&tp, &osmo_gsm44068_att_tlvdef, ie, remaining_len, 0, 0); + if (rc < 0) + return _ie_invalid(); + + /* Talker priority */ + tlv = TLVP_GET(&tp, OSMO_GSM44068_IEI_TALKER_PRIORITY); + if (tlv && tlv->len) { + *with_talker_prio = true; + *talker_prio = tlv->val[0] & 0x07; + } else + *with_talker_prio = false; + + return 0; +} + +/* + * GCC/BCC state machine - handles calling subscriber process + */ + +static const struct value_string vgcs_gcc_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_GCC_EV_NET_SETUP), + OSMO_VALUE_STRING(VGCS_GCC_EV_NET_TERM), + OSMO_VALUE_STRING(VGCS_GCC_EV_USER_SETUP), + OSMO_VALUE_STRING(VGCS_GCC_EV_USER_TERM), + OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_ESTABLISHED), + OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_ASSIGN_CPL), + OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_ASSIGN_FAIL), + OSMO_VALUE_STRING(VGCS_GCC_EV_BSS_RELEASED), + OSMO_VALUE_STRING(VGCS_GCC_EV_TIMEOUT), + { } +}; + +static int gcc_establish_bss(struct gsm_trans *trans) +{ + struct gsm_network *net = trans->net; + struct vgcs_mgw_ep *mgw = NULL; + struct mgcp_client *mgcp_client; + struct gcr *gcr; + struct gcr_bss *b; + struct gcr_cell *c; + struct vgcs_bss *bss; + struct vgcs_bss_cell *cell; + struct osmo_fsm_inst *fi; + struct ran_peer *rp; + + /* Failure should not happen, because it has been checked before. */ + gcr = gcr_by_callref(trans->net, trans->type, trans->callref); + if (!gcr) + return -EINVAL; + + /* Allocate MGW endpoint. */ + mgcp_client = mgcp_client_pool_get(trans->net->mgw.mgw_pool); + if (!mgcp_client) { + LOG_GCC(trans, LOGL_ERROR, "No MGW client, please check config.\n"); + goto err_mgw; + } + fi = osmo_fsm_inst_alloc(&vgcs_mgw_ep_fsm, net, NULL, LOGL_DEBUG, NULL); + if (!fi) { + LOG_GCC(trans, LOGL_ERROR, "No memory for VGCS MSG state machine.\n"); + goto err_mgw; + } + osmo_fsm_inst_update_id(fi, "vgcs-mgw-ep"); + osmo_fsm_inst_state_chg(fi, VGCS_MGW_EP_ST_ACTIVE, 0, 0); + mgw = talloc_zero(fi, struct vgcs_mgw_ep); + if (!mgw) { + LOG_GCC(trans, LOGL_ERROR, "No memory for MGW ep structure.\n"); + osmo_fsm_inst_free(fi); + goto err_mgw; + } + mgw->fi = fi; + fi->priv = mgw; + INIT_LLIST_HEAD(&mgw->cell_list); + mgw->mgw_ep = osmo_mgcpc_ep_alloc(mgw->fi, VGCS_MGW_EP_EV_FREE, + mgcp_client, trans->net->mgw.tdefs, mgw->fi->id, + "%s", mgcp_client_rtpbridge_wildcard(mgcp_client)); + if (!mgw->mgw_ep) { + LOG_GCC(trans, LOGL_ERROR, "No memory for MGW endpoint state machine.\n"); + goto err_mgw; + } + + /* Create BSS list structures. */ + LOG_GCC(trans, LOGL_DEBUG, "Creating BSS list structure with cell list structures.\n"); + llist_for_each_entry(b, &gcr->bss_list, list) { + LOG_GCC(trans, LOGL_DEBUG, " -> BSS with PC %s.\n", osmo_ss7_pointcode_print(NULL, b->pc)); + /* Resolve ran_peer. */ + rp = ran_peer_for_pc(trans->net, b->pc); + if (!rp) { + LOG_GCC(trans, LOGL_ERROR, "Failed to resolve point code %s, skipping BSS!\n", + osmo_ss7_pointcode_print(NULL, b->pc)); + continue; + } + /* Create state machine. */ + fi = osmo_fsm_inst_alloc(&vgcs_bss_fsm, net, NULL, LOGL_DEBUG, NULL); + if (!fi) { + LOG_GCC(trans, LOGL_ERROR, "No memory for state machine.\n"); + break; + } + /* Create call structure. */ + bss = talloc_zero(fi, struct vgcs_bss); + if (!bss) { + LOG_GCC(trans, LOGL_ERROR, "No memory for BSS call structure.\n"); + osmo_fsm_inst_free(fi); + break; + } + bss->fi = fi; + fi->priv = bss; + INIT_LLIST_HEAD(&bss->cell_list); + bss->trans = trans; + bss->trans_type = trans->type; + bss->callref = trans->callref; + bss->pc = b->pc; + /* Create ran connection. */ + bss->conn = ran_conn_create_outgoing(rp); + if (!bss->conn) { + LOG_GCC(trans, LOGL_ERROR, "Failed to create RAN connection.\n"); + osmo_fsm_inst_free(bss->fi); + continue; + } + bss->conn->vgcs.bss = bss; + /* Create cell list structures. */ + llist_for_each_entry(c, &b->cell_list, list) { + LOG_GCC(trans, LOGL_DEBUG, " -> Cell ID %d.\n", c->cell_id); + /* Create state machine. */ + fi = osmo_fsm_inst_alloc(&vgcs_cell_fsm, net, NULL, LOGL_DEBUG, NULL); + if (!fi) { + LOG_GCC(trans, LOGL_ERROR, "No memory for state machine.\n"); + break; + } + /* Create cell structure. */ + cell = talloc_zero(fi, struct vgcs_bss_cell); + if (!cell) { + LOG_GCC(trans, LOGL_ERROR, "No memory for BSS cell structure.\n"); + osmo_fsm_inst_free(fi); + break; + } + cell->fi = fi; + fi->priv = cell; + osmo_fsm_inst_update_id_f(cell->fi, "vgcs-cell-%d", c->cell_id); + cell->trans_type = trans->type; + cell->callref = trans->callref; + cell->pc = b->pc; + cell->cell_id = c->cell_id; + cell->call_id = trans->call_id; + /* Create ran connection. */ + cell->conn = ran_conn_create_outgoing(rp); + if (!cell->conn) { + LOG_GCC(trans, LOGL_ERROR, "Failed to create RAN connection.\n"); + osmo_fsm_inst_free(cell->fi); + continue; + } + cell->conn->vgcs.cell = cell; + /* Attach to cell list of BSS and MGW endpoint */ + llist_add_tail(&cell->list_bss, &bss->cell_list); + cell->bss = bss; + llist_add_tail(&cell->list_mgw, &mgw->cell_list); + cell->mgw = mgw; + } + /* No cell? */ + if (llist_empty(&bss->cell_list)) { + LOG_GCC(trans, LOGL_DEBUG, " -> No Cell in this BSS.\n"); + osmo_fsm_inst_free(bss->fi); + break; + } + /* Attach to transaction list */ + llist_add_tail(&bss->list, &trans->gcc.bss_list); + /* Trigger VGCS/VBS SETUP */ + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_SETUP, NULL); + } + /* No BSS? */ + if (llist_empty(&trans->gcc.bss_list)) { + /* Also destroy MGW, because this list is empty too! */ + LOG_GCC(trans, LOGL_NOTICE, "No BSS found, please check your VTY configuration and add cells.\n"); + goto err_mgw; + } + return 0; + +err_mgw: + if (mgw) { + if (mgw->mgw_ep) { + /* This will also free FSM instance and vgcs_mgw_ep structure. */ + osmo_fsm_inst_dispatch(mgw->fi, VGCS_MGW_EP_EV_CLEAR, NULL); + return -EINVAL; + } + osmo_fsm_inst_free(mgw->fi); + } + return -EINVAL; +} + +/* Send Assignment Request to the calling subscriber. + * This is used to assign the subscriber from early assigned channel to the VGCS/VBS channel. */ +static int gcc_assign(struct gsm_trans *trans) +{ + struct ran_msg tx_ran_msg; + struct gsm0808_channel_type channel_type; + struct vgcs_bss *bss = NULL, *b; + + /* No assignment, because the calling subscriber is already assigned or there is no calling subscriber. */ + if (!trans->msc_a) + return 0; + + /* Check calling subscriber's MSC */ + struct ran_conn *conn = msub_ran_conn(trans->msc_a->c.msub); + if (!conn) { + LOG_GCC(trans, LOGL_ERROR, "Calling subscriber has no ran_conn????\n"); + return -EINVAL; + } + llist_for_each_entry(b, &trans->gcc.bss_list, list) { + if (osmo_sccp_addr_ri_cmp(&conn->ran_peer->peer_addr, &b->conn->ran_peer->peer_addr)) + continue; + bss = b; + break; + } + if (!bss) { + LOG_GCC(trans, LOGL_ERROR, "Calling subscriber comes from BSC that has no VGCS call.\n"); + return -EINVAL; + } + + /* For now we support GSM/FR V1 only. This shall be supported by all MS. */ + channel_type = (struct gsm0808_channel_type) { + .ch_indctr = GSM0808_CHAN_SPEECH, + .ch_rate_type = GSM0808_SPEECH_FULL_BM, + .perm_spch_len = 1, + .perm_spch[0] = GSM0808_PERM_FR1, + }; + + /* Send assignment to VGCS channel */ + tx_ran_msg = (struct ran_msg) { + .msg_type = RAN_MSG_ASSIGNMENT_COMMAND, + .assignment_command = { + .channel_type = &channel_type, + .callref_present = true, + .callref = { + .sf = (trans->type == TRANS_GCC), + }, + }, + }; + osmo_store32be_ext(trans->callref >> 3, &tx_ran_msg.assignment_command.callref.call_ref_hi, 3); + tx_ran_msg.assignment_command.callref.call_ref_lo = trans->callref & 0x7; + if (msc_a_ran_down(trans->msc_a, MSC_ROLE_I, &tx_ran_msg)) { + LOG_GCC(trans, LOGL_ERROR, "Cannot send Assignment\n"); + return -EIO; + } + + /* Assign Talker to BSS of the calling subscriber. */ + trans->gcc.uplink_bss = bss; + + return 0; +} + +/* Send CONNECT to the calling subscriber. */ +static void gcc_connect(struct gsm_trans *trans) +{ + uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC; + int rc; + + /* Send CONNECT towards MS. */ + rc = gsm44068_tx_connect(trans, + pdisc | (trans->transaction_id << 4), + trans->callref, 0, 0, 1, 0, 0, 0, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send CONNECT towards MS. Continue anyway.\n"); +} + +/* Release dedicated (SDCCH) channel of calling subscriber after assigning to VGCS */ +static void release_msc_a(struct gsm_trans *trans) +{ + struct msc_a *msc_a = trans->msc_a; + + if (!msc_a) + return; + + trans->msc_a = NULL; + switch (trans->type) { + case TRANS_GCC: + msc_a_put(msc_a, MSC_A_USE_GCC); + break; + case TRANS_BCC: + msc_a_put(msc_a, MSC_A_USE_BCC); + break; + default: + break; + } +} + +/* Send TERMINATE to the calling/talking subscriber, then destroy transaction. */ +static void gcc_terminate_and_destroy(struct gsm_trans *trans, enum osmo_gsm44068_cause cause) +{ + uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC; + int rc; + + /* Send TERMINATION towards MS. */ + rc = gsm44068_tx_termination(trans->msc_a, trans->gcc.uplink_bss, + pdisc | (trans->transaction_id << 4), + OSMO_GSM44068_MSGT_TERMINATION, + cause, NULL, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS. Continue anyway.\n"); + + /* Destroy transaction, note that also _gsm44068_gcc_trans_free() will be called by trans_free(). + * There the complete state machine is destroyed. */ + trans->callref = 0; + trans_free(trans); +} + +/* Send TERMINATION REJECT to the calling/talking subscriber. */ +static void gcc_termination_reject(struct gsm_trans *trans, enum osmo_gsm44068_cause cause) +{ + uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC; + int rc; + + /* Send TERMINATION towards MS. */ + rc = gsm44068_tx_termination(trans->msc_a, trans->gcc.uplink_bss, + pdisc | (trans->transaction_id << 4), + OSMO_GSM44068_MSGT_TERMINATION_REJECT, + cause, NULL, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION REJECT towards MS.\n"); +} + +/* Start inactivity timer. + * This timer is used to terminate the call, if the radio connection to the caller gets lost. */ +static void start_inactivity_timer(struct gsm_trans *trans) +{ + if (trans->gcc.inactivity_to) { + LOG_GCC(trans, LOGL_DEBUG, "Set inactivity timer to %d seconds.\n", trans->gcc.inactivity_to); + osmo_timer_schedule(&trans->gcc.timer_inactivity, trans->gcc.inactivity_to, 0); + } +} + +static void stop_inactivity_timer(struct gsm_trans *trans) +{ + if (osmo_timer_pending(&trans->gcc.timer_inactivity)) { + LOG_GCC(trans, LOGL_DEBUG, "Stop pending inactivity timer.\n"); + osmo_timer_del(&trans->gcc.timer_inactivity); + } +} + +static void inactivity_timer_cb(void *data) +{ + struct gsm_trans *trans = data; + + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_TIMEOUT, NULL); +} + +/* Set the parameters of the talker. (downlink mute/unmute, uplink unmute, COMM=T, originator) */ +static int set_parameter(struct gsm_trans *trans) +{ + uint8_t pdisc = (trans->type == TRANS_GCC) ? GSM48_PDISC_GROUP_CC : GSM48_PDISC_BCAST_CC; + int rc; + + rc = gsm44068_tx_set_parameter(trans, pdisc | (trans->transaction_id << 4), + !trans->gcc.mute_talker, 1, 1, trans->gcc.uplink_originator); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send SET PARAMETER towards MS.\n"); + return rc; +} + +/* Check in which cell the uplink is used and set "uplink_cell". */ +static int set_uplink_cell(struct vgcs_bss *bss, struct gsm0808_cell_id *cell_id_ie, uint16_t cell_id) +{ + struct vgcs_bss_cell *cell; + + if (cell_id_ie) { + /* Get cell ID to determine talker channel. */ + switch (cell_id_ie->id_discr) { + case CELL_IDENT_CI: + cell_id = cell_id_ie->id.ci; + break; + case CELL_IDENT_LAC_AND_CI: + cell_id = cell_id_ie->id.lac_and_ci.ci; + break; + default: + LOG_BSS(bss, LOGL_DEBUG, "Cannot idenitfy cell, please fix!\n"); + return -EINVAL; + } + } + + /* Search for cell ID. */ + bss->trans->gcc.uplink_cell = NULL; + llist_for_each_entry(cell, &bss->cell_list, list_bss) { + if (cell->cell_id == cell_id) { + LOG_BSS(bss, LOGL_DEBUG, "Talker is talking on cell %d.\n", cell->cell_id); + bss->trans->gcc.uplink_cell = cell; + return 0; + } + } + + LOG_BSS(bss, LOGL_DEBUG, "Cell ID %d is not in list of current BSS, please fix!\n", cell_id); + return -EINVAL; +} + +/* Set the MGW conference mode. + * All cells are listening to the conference. If there is a talker, this cell is also transmitting to the conference. */ +static int set_mgw_conference(struct gsm_trans *trans) +{ + struct vgcs_bss *bss; + struct vgcs_bss_cell *cell; + struct rtp_stream *rtps; + int rc; + + /* All cells without talker are listening */ + llist_for_each_entry(bss, &trans->gcc.bss_list, list) { + llist_for_each_entry(cell, &bss->cell_list, list_bss) { + if (!(rtps = cell->rtps)) + continue; + if (rtps->crcx_conn_mode != MGCP_CONN_SEND_ONLY) { + LOG_CELL(cell, LOGL_DEBUG, "Setting cell %d into listening mode.\n", cell->cell_id); + rtp_stream_set_mode(rtps, MGCP_CONN_SEND_ONLY); + rc = rtp_stream_commit(rtps); + if (rc < 0) + LOG_CELL(cell, LOGL_ERROR, "Failed to commit parameters to RTP stream " + "for cell %d.\n", cell->cell_id); + } + } + } + + if (trans->gcc.uplink_cell && trans->gcc.uplink_cell->rtps) { + cell = trans->gcc.uplink_cell; + rtps = cell->rtps; + LOG_CELL(cell, LOGL_DEBUG, "Setting cell %d into listening mode.\n", cell->cell_id); + rtp_stream_set_mode(rtps, MGCP_CONN_CONFECHO); + rc = rtp_stream_commit(rtps); + if (rc < 0) + LOG_CELL(cell, LOGL_ERROR, "Failed to commit parameters to RTP stream " + "for cell %d.\n", cell->cell_id); + } + + return 0; +} + +static void _assign_complete(struct gsm_trans *trans, bool send_connect) +{ + uint16_t cell_id; + + OSMO_ASSERT(trans->msc_a); + + /* Change state. */ + osmo_fsm_inst_state_chg(trans->gcc.fi, VGCS_GCC_ST_N2_CALL_ACTIVE, 0, 0); + /* Get cell ID. */ + cell_id = trans->msc_a->via_cell.cell_identity; + /* Releasing dedicated channel. */ + release_msc_a(trans); + /* Send CONNECT to the calling subscriber. */ + if (send_connect) + gcc_connect(trans); + /* Set parameter. */ + set_parameter(trans); + /* Start inactivity timer, if uplink is free. */ + if (!trans->gcc.uplink_busy) + start_inactivity_timer(trans); + /* Set cell of current talker. */ + set_uplink_cell(trans->gcc.uplink_bss, NULL, cell_id); + /* Set MGW conference. */ + set_mgw_conference(trans); +} + +#define CONNECT_OPTION false + +static void vgcs_gcc_fsm_n0_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + int rc; + + switch (event) { + case VGCS_GCC_EV_NET_SETUP: + /* Establish call towards all BSSs. */ + LOG_GCC(trans, LOGL_DEBUG, "Setup by network, trying to establish cells.\n"); + rc = gcc_establish_bss(trans); + if (rc < 0) { + LOG_GCC(trans, LOGL_NOTICE, "Failed to setup call to any cell.\n"); + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + } + /* Keep state until established or released. */ + break; + case VGCS_GCC_EV_NET_TERM: + LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n"); + /* Destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_USER_SETUP: + LOG_GCC(trans, LOGL_DEBUG, "Setup by MS, trying to establish cells.\n"); + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_N1_CALL_INITIATED, 0, 0); + /* Establish call towards all BSSs. */ + rc = gcc_establish_bss(trans); + if (rc < 0) { + LOG_GCC(trans, LOGL_NOTICE, "Failed to setup call to any cell.\n"); + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + } + if (CONNECT_OPTION) { + /* Send CONNECT to the calling subscriber. */ + gcc_connect(trans); + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_N3_CALL_EST_PROC, 0, 0); + } + break; + case VGCS_GCC_EV_BSS_ESTABLISHED: + LOG_GCC(trans, LOGL_DEBUG, "All cells establised, for a group call, sending CONNECT to caller.\n"); + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_GCC_ST_N2_CALL_ACTIVE, 0, 0); + /* Start inactivity timer, if uplink is free. */ + if (!trans->gcc.uplink_busy) + start_inactivity_timer(trans); + break; + case VGCS_GCC_EV_BSS_RELEASED: + LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n"); + /* Send TERMINATE to the calling subscriber. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_gcc_fsm_n1_call_initiated(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + int rc; + + switch (event) { + case VGCS_GCC_EV_NET_TERM: + LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n"); + /* Destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_USER_TERM: + LOG_GCC(trans, LOGL_DEBUG, "Termination by user, destroying call.\n"); + /* Send TERMINATE to the calling subscriber and destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_BSS_ESTABLISHED: + LOG_GCC(trans, LOGL_DEBUG, "All cells establised, for a group call, assign caller to VGCS.\n"); + /* Send assignment to the calling subscriber. */ + rc = gcc_assign(trans); + if (rc < 0) { + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + } + break; + case VGCS_GCC_EV_BSS_ASSIGN_CPL: + LOG_GCC(trans, LOGL_DEBUG, "Assignment complete, sending CONNECT to caller, releasing channel.\n"); + /* Handle assignment complete */ + _assign_complete(trans, true); + break; + case VGCS_GCC_EV_BSS_ASSIGN_FAIL: + LOG_GCC(trans, LOGL_DEBUG, "Assignment failed, releasing call.\n"); + /* Send TERMINATE to the calling subscriber. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + case VGCS_GCC_EV_BSS_RELEASED: + LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n"); + /* Send TERMINATE to the calling subscriber. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_gcc_fsm_n2_call_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + + switch (event) { + case VGCS_GCC_EV_NET_TERM: + LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n"); + /* Destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_USER_TERM: + if (!trans->gcc.uplink_originator) { + LOG_GCC(trans, LOGL_ERROR, "Termination by user, but it is not the originator.\n"); + gcc_termination_reject(trans, OSMO_GSM44068_CAUSE_USER_NOT_ORIGINATOR); + break; + } + LOG_GCC(trans, LOGL_DEBUG, "Termination by user, destroying call.\n"); + /* Send TERMINATE to the calling subscriber and destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_BSS_RELEASED: + LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n"); + /* Send TERMINATE to the calling subscriber. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + case VGCS_GCC_EV_TIMEOUT: + LOG_GCC(trans, LOGL_DEBUG, "Termination by inactivity timer, destroying call.\n"); + /* Destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_gcc_fsm_n3_call_est_proc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_trans *trans = fi->priv; + int rc; + + switch (event) { + case VGCS_GCC_EV_NET_TERM: + LOG_GCC(trans, LOGL_DEBUG, "Termination by network, destroying call.\n"); + /* Destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_USER_TERM: + LOG_GCC(trans, LOGL_DEBUG, "Termination by user, destroying call.\n"); + /* Send TERMINATE to the calling subscriber and destroy group call in all cells. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + case VGCS_GCC_EV_BSS_ESTABLISHED: + LOG_GCC(trans, LOGL_DEBUG, "All cells establised, for a group call, assign caller to VGCS.\n"); + /* Send assignment to the calling subscriber. */ + rc = gcc_assign(trans); + if (rc < 0) { + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + } + break; + case VGCS_GCC_EV_BSS_ASSIGN_CPL: + LOG_GCC(trans, LOGL_DEBUG, "Assignment complete, sending CONNECT to caller, releasing channel.\n"); + /* Handle assignment complete */ + _assign_complete(trans, false); + break; + case VGCS_GCC_EV_BSS_ASSIGN_FAIL: + LOG_GCC(trans, LOGL_DEBUG, "Assignment failed, releasing call.\n"); + /* Send TERMINATE to the calling subscriber. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + case VGCS_GCC_EV_BSS_RELEASED: + LOG_GCC(trans, LOGL_DEBUG, "All group call in all cells failed, destroying call.\n"); + /* Send TERMINATE to the calling subscriber. */ + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + break; + default: + OSMO_ASSERT(false); + } +} + +static const struct osmo_fsm_state vgcs_gcc_fsm_states[] = { + [VGCS_GCC_ST_N0_NULL] = { + .name = "NULL (N0)", + .in_event_mask = S(VGCS_GCC_EV_NET_SETUP) | + S(VGCS_GCC_EV_NET_TERM) | + S(VGCS_GCC_EV_USER_SETUP) | + S(VGCS_GCC_EV_BSS_ESTABLISHED) | + S(VGCS_GCC_EV_BSS_RELEASED), + .out_state_mask = S(VGCS_GCC_ST_N1_CALL_INITIATED) | + S(VGCS_GCC_ST_N2_CALL_ACTIVE), + .action = vgcs_gcc_fsm_n0_null, + }, + [VGCS_GCC_ST_N1_CALL_INITIATED] = { + .name = "CALL INITATED (N1)", + .in_event_mask = S(VGCS_GCC_EV_NET_TERM) | + S(VGCS_GCC_EV_USER_TERM) | + S(VGCS_GCC_EV_BSS_ESTABLISHED) | + S(VGCS_GCC_EV_BSS_ASSIGN_CPL) | + S(VGCS_GCC_EV_BSS_ASSIGN_FAIL) | + S(VGCS_GCC_EV_BSS_RELEASED), + .out_state_mask = S(VGCS_GCC_ST_N0_NULL) | + S(VGCS_GCC_ST_N2_CALL_ACTIVE) | + S(VGCS_GCC_ST_N3_CALL_EST_PROC), + .action = vgcs_gcc_fsm_n1_call_initiated, + }, + [VGCS_GCC_ST_N2_CALL_ACTIVE] = { + .name = "CALL ACTIVE (N2)", + .in_event_mask = S(VGCS_GCC_EV_NET_TERM) | + S(VGCS_GCC_EV_USER_TERM) | + S(VGCS_GCC_EV_BSS_RELEASED) | + S(VGCS_GCC_EV_TIMEOUT), + .out_state_mask = S(VGCS_GCC_ST_N0_NULL), + .action = vgcs_gcc_fsm_n2_call_active, + }, + [VGCS_GCC_ST_N3_CALL_EST_PROC] = { + .name = "CALL EST PROCEEDING (N3)", + .in_event_mask = S(VGCS_GCC_EV_NET_TERM) | + S(VGCS_GCC_EV_USER_TERM) | + S(VGCS_GCC_EV_BSS_ESTABLISHED) | + S(VGCS_GCC_EV_BSS_ASSIGN_CPL) | + S(VGCS_GCC_EV_BSS_ASSIGN_FAIL) | + S(VGCS_GCC_EV_BSS_RELEASED), + .out_state_mask = S(VGCS_GCC_ST_N2_CALL_ACTIVE) | + S(VGCS_GCC_ST_N0_NULL), + .action = vgcs_gcc_fsm_n3_call_est_proc, + }, + // We don't need a state to wait for the group call to be terminated in all cells +}; + +static struct osmo_fsm vgcs_bcc_fsm = { + .name = "bcc", + .states = vgcs_gcc_fsm_states, + .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states), + .log_subsys = DBCC, + .event_names = vgcs_gcc_fsm_event_names, +}; + +static struct osmo_fsm vgcs_gcc_fsm = { + .name = "gcc", + .states = vgcs_gcc_fsm_states, + .num_states = ARRAY_SIZE(vgcs_gcc_fsm_states), + .log_subsys = DGCC, + .event_names = vgcs_gcc_fsm_event_names, +}; + +const char *vgcs_bcc_gcc_state_name(struct osmo_fsm_inst *fi) +{ + return vgcs_gcc_fsm_states[fi->state].name; +} + +static int update_uplink_state(struct vgcs_bss *bss, bool uplink_busy); + +/* Receive RR messages from calling subscriber, prior assignment to VGCS/VBS. */ +int gsm44068_rcv_rr(struct msc_a *msc_a, struct msgb *msg) +{ + struct gsm_trans *trans = NULL; + struct gsm48_hdr *gh; + uint8_t msg_type; + + gh = msgb_l3(msg); + msg_type = gsm48_hdr_msg_type(gh); + + /* Find transaction. */ + trans = trans_find_by_type(msc_a, TRANS_GCC); + if (!trans) + trans = trans_find_by_type(msc_a, TRANS_BCC); + + if (!trans) { + LOG_GCC(trans, LOGL_ERROR, "No VGCS/VBS transaction.\n"); + return -EINVAL; + } + + /* In case the phone releases uplink prior being assigned to a VGCS */ + if (msg_type == GSM48_MT_RR_UPLINK_RELEASE) { + struct vgcs_bss *bss; + + LOG_GCC(trans, LOGL_INFO, "Received UPLINK RELEASE on initial channel.\n"); + /* Clear the busy flag and unblock all cells. */ + trans->gcc.uplink_bss = NULL; + trans->gcc.uplink_cell = NULL; + trans->gcc.uplink_busy = false; + llist_for_each_entry(bss, &trans->gcc.bss_list, list) { + /* Update uplink state. */ + update_uplink_state(bss, trans->gcc.uplink_busy); + } + /* Start inactivity timer. */ + start_inactivity_timer(bss->trans); + /* Next, the MS will switch to the VGCS as listener. Nothing else to do here. */ + } + + return 0; +} + +/* Allocation of transaction for group call */ +static struct gsm_trans *trans_alloc_vgcs(struct gsm_network *net, + struct vlr_subscr *vsub, + enum trans_type trans_type, uint8_t transaction_id, + uint32_t callref, + struct gcr *gcr, + bool uplink_busy) +{ + struct gsm_trans *trans; + + trans = trans_alloc(net, vsub, trans_type, transaction_id, callref); + if (!trans) { + LOG_GCC(trans, LOGL_ERROR, "No memory for trans.\n"); + return NULL; + } + /* The uplink is busy when the call is started until the calling subscriber releases. */ + trans->gcc.uplink_busy = uplink_busy; + trans->gcc.uplink_originator = true; + INIT_LLIST_HEAD(&trans->gcc.bss_list); + trans->gcc.inactivity_to = gcr->timeout; + trans->gcc.mute_talker = gcr->mute_talker; + trans->gcc.timer_inactivity.data = trans; + trans->gcc.timer_inactivity.cb = inactivity_timer_cb; + trans->gcc.fi = osmo_fsm_inst_alloc((trans_type == TRANS_GCC) ? &vgcs_gcc_fsm : &vgcs_bcc_fsm, + trans, trans, LOGL_DEBUG, NULL); + if (!trans->gcc.fi) { + LOG_GCC(trans, LOGL_ERROR, "No memory for state machine.\n"); + trans_free(trans); + return NULL; + } + + return trans; +} + +/* Create transaction from incoming voice group/broadcast call. */ +static struct gsm_trans *trans_create_bcc_gcc(struct msc_a *msc_a, enum trans_type trans_type, uint8_t transaction_id, + uint8_t pdisc, uint8_t msg_type, uint32_t callref) +{ + struct gsm_network *net; + struct vlr_subscr *vsub; + struct gsm_trans *trans = NULL; + struct gcr *gcr; + int rc; + + if (!msc_a) { + LOG_GCC(trans, LOGL_ERROR, "Invalid conn: no msc_a\n"); + return NULL; + } + net = msc_a_net(msc_a); + vsub = msc_a_vsub(msc_a); + + if (!vsub) { + LOG_GCC(trans, LOGL_ERROR, "Invalid conn: no subscriber\n"); + return NULL; + } + + /* An earlier CM Service Request for this CC message now has concluded */ + if (!osmo_use_count_by(&msc_a->use_count, + (trans_type == TRANS_GCC) ? MSC_A_USE_CM_SERVICE_GCC : MSC_A_USE_CM_SERVICE_BCC)) + LOG_MSC_A(msc_a, LOGL_ERROR, + "Creating new %s transaction without prior CM Service Request.\n", + get_value_string(trans_type_names, trans_type)); + else + msc_a_put(msc_a, + (trans_type == TRANS_GCC) ? MSC_A_USE_CM_SERVICE_GCC : MSC_A_USE_CM_SERVICE_BCC); + + /* A transaction must be created with a SETUP message. */ + if (msg_type != OSMO_GSM44068_MSGT_IMMEDIATE_SETUP + && msg_type != OSMO_GSM44068_MSGT_SETUP + && msg_type != OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2) { + LOG_GCC(trans, LOGL_ERROR, "No transaction and message is not a SETUP.\n"); + return NULL; + } + + /* Check if callref already exists. */ + trans = trans_find_by_callref(net, trans_type, callref); + if (trans) { + LOG_GCC(trans, LOGL_INFO, "Call to existing %s with callref %s, rejecting!\n", + trans_type_name(trans_type), gsm44068_group_id_string(callref)); + rc = gsm44068_tx_termination(msc_a, NULL, + pdisc | (transaction_id << 4), + OSMO_GSM44068_MSGT_TERMINATION, + OSMO_GSM44068_CAUSE_BUSY, NULL, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n"); + return 0; + } + + /* Check GCR for Group ID. */ + gcr = gcr_by_callref(net, trans_type, callref); + if (!gcr) { + LOG_GCC(trans, LOGL_INFO, "No Group configured for %s callref %s, rejecting!\n", + trans_type_name(trans_type), gsm44068_group_id_string(callref)); + // FIXME: Better cause value for a group that does not exist ? + rc = gsm44068_tx_termination(msc_a, NULL, + pdisc | (transaction_id << 4), + OSMO_GSM44068_MSGT_TERMINATION, + OSMO_GSM44068_CAUSE_REQUESTED_SERVICE_NOT_SUB, NULL, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n"); + return 0; + } + + /* Create transaction, uplink is busy. */ + trans = trans_alloc_vgcs(net, vsub, trans_type, transaction_id, callref, gcr, true); + if (!trans) { + rc = gsm44068_tx_termination(msc_a, NULL, + pdisc | (transaction_id << 4), + OSMO_GSM44068_MSGT_TERMINATION, + OSMO_GSM44068_CAUSE_NETWORK_FAILURE, NULL, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n"); + return NULL; + } + + if (osmo_fsm_inst_dispatch(msc_a->c.fi, MSC_A_EV_TRANSACTION_ACCEPTED, trans)) { + LOG_MSC_A(msc_a, LOGL_ERROR, "Not allowed to accept %s transaction.\n", + get_value_string(trans_type_names, trans_type)); + gcc_terminate_and_destroy(trans, OSMO_GSM44068_CAUSE_NETWORK_FAILURE); + return NULL; + } + + /* Assign transaction */ + msc_a_get(msc_a, (trans_type == TRANS_GCC) ? MSC_A_USE_GCC : MSC_A_USE_BCC); + trans->msc_a = msc_a; + trans->dlci = 0; /* main DCCH */ + + return trans; +} + +/* Receive GCC/BCC messages from calling subscriber, depending on the PDISC used. */ +int gsm44068_rcv_bcc_gcc(struct msc_a *msc_a, struct gsm_trans *trans, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t msg_type = gsm48_hdr_msg_type(gh); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + uint8_t transaction_id = gsm48_hdr_trans_id_flip_ti(gh); + enum trans_type trans_type = (pdisc == GSM48_PDISC_GROUP_CC) ? TRANS_GCC : TRANS_BCC; + + uint8_t key_seq; + bool talker_prio_requested; + bool with_talker_prio; + uint8_t talker_prio; + struct gsm48_classmark2 cm2; + struct osmo_mobile_identity mi; + uint32_t callref; + bool with_prio; + uint8_t prio; + char user_user[64] = ""; + uint8_t cause; + uint8_t diag[256]; + uint8_t diag_len; + bool with_call_state; + enum osmo_gsm44068_call_state call_state; + bool with_state_attrs; + uint8_t da, ua, comm, oi; + int rc = 0; + + /* Remove sequence number (bit 7) from message type. */ + msg_type &= 0xbf; + + /* Parse messages. */ + switch (msg_type) { + case OSMO_GSM44068_MSGT_SETUP: + rc = gsm44068_rx_setup(msg, &talker_prio_requested, &talker_prio, &callref, &with_prio, &prio, + user_user); + break; + case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP: + case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2: + rc = gsm44068_rx_immediate_setup(msg, &talker_prio, &key_seq, &cm2, &mi, &callref, &with_prio, &prio, + user_user); + break; + case OSMO_GSM44068_MSGT_STATUS: + rc = gsm44068_rx_status(msg, &cause, diag, &diag_len, &with_call_state, &call_state, + &with_state_attrs, &da, &ua, &comm, &oi); + break; + case OSMO_GSM44068_MSGT_TERMINATION_REQUEST: + rc = gsm44068_rx_termination_req(msg, &callref, &with_prio, &prio, &with_talker_prio, &talker_prio); + break; + default: + LOG_GCC(trans, LOGL_ERROR, "Invalid message type: 0x%02x\n", msg_type); + return -EINVAL; + } + if (rc < 0) + return rc; + + /* Find transaction, if called from msc_a. */ + if (!trans) + trans = trans_find_by_id(msc_a, trans_type, transaction_id); + + /* Create transaction for SETUP message. */ + if (!trans) { + trans = trans_create_bcc_gcc(msc_a, trans_type, transaction_id, pdisc, msg_type, callref); + if (!trans) + return -EINVAL; + } else { + /* A phone may not call while a VGCS is already active */ + if (msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP + || msg_type == OSMO_GSM44068_MSGT_SETUP + || msg_type == OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2) { + LOG_GCC(trans, LOGL_ERROR, "Received SETUP while call is already set up, rejecting.\n"); + rc = gsm44068_tx_termination(msc_a, NULL, + pdisc | (transaction_id << 4), + OSMO_GSM44068_MSGT_TERMINATION, + OSMO_GSM44068_CAUSE_NETWORK_FAILURE, NULL, 0); + if (rc < 0) + LOG_GCC(trans, LOGL_ERROR, "Failed to send TERMINATION towards MS.\n"); + return -EINVAL; + } + } + + /* Handle received GCC messages (trigger state machine). */ + switch (msg_type) { + case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP: + case OSMO_GSM44068_MSGT_SETUP: + case OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2: + LOG_GCC(trans, LOGL_INFO, "Received SETUP.\n"); + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_USER_SETUP, NULL); + break; + case OSMO_GSM44068_MSGT_STATUS: + LOG_GCC(trans, LOGL_NOTICE, "Received STATUS with cause %d (%s).\n", cause, + get_value_string(osmo_gsm44068_cause_names, cause)); + if (diag_len) + LOG_GCC(trans, LOGL_NOTICE, " -> diagnostics: %s\n", osmo_hexdump(diag, diag_len)); + if (with_call_state) + LOG_GCC(trans, LOGL_NOTICE, " -> call state %s\n", + get_value_string(osmo_gsm44068_call_state_names, call_state)); + break; + case OSMO_GSM44068_MSGT_TERMINATION_REQUEST: + LOG_GCC(trans, LOGL_INFO, "Received TERMINATRION REQUEST.\n"); + if (callref != trans->callref) { + LOG_GCC(trans, LOGL_NOTICE, "Received callref 0x%x does not match!\n", callref); + break; + } + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_USER_TERM, NULL); + break; + } + + return 0; +} + +static void bss_clear(struct vgcs_bss *bss, uint8_t cause, bool notify_trans); + +/* Call Control Specific transaction release. + * gets called by trans_free, DO NOT CALL YOURSELF! */ +void gsm44068_bcc_gcc_trans_free(struct gsm_trans *trans) +{ + struct vgcs_bss *bss, *bss2; + + /* Free FSM. */ + if (trans->gcc.fi) { + osmo_fsm_inst_state_chg(trans->gcc.fi, VGCS_GCC_ST_N0_NULL, 0, 0); + osmo_fsm_inst_term(trans->gcc.fi, OSMO_FSM_TERM_REGULAR, NULL); + } + + /* Remove relations to cells. + * We must loop safe, because bss_clear() will detach every call control instance from list. */ + llist_for_each_entry_safe(bss, bss2, &trans->gcc.bss_list, list) + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_CLEAR, NULL); + + /* Stop inactivity timer. */ + stop_inactivity_timer(trans); +} + +/* Create a new call from VTY command. */ +const char *vgcs_vty_initiate(struct gsm_network *gsmnet, struct gcr *gcr) +{ + enum trans_type trans_type; + uint32_t callref; + struct gsm_trans *trans; + + /* Get callref from stored suffix. Caller cannot choose a prefix. */ + trans_type = gcr->trans_type; + callref = atoi(gcr->group_id); + + /* Check if callref already exists. */ + trans = trans_find_by_callref(gsmnet, trans_type, callref); + if (trans) { + LOG_GCC(trans, LOGL_INFO, "Call to existing %s with callref %s, rejecting!\n", + trans_type_name(trans_type), gsm44068_group_id_string(callref)); + return "Call already exists."; + } + + /* Create transaction, uplink is free. */ + trans = trans_alloc_vgcs(gsmnet, NULL, trans_type, 0, callref, gcr, false); + if (!trans) { + LOG_GCC(trans, LOGL_ERROR, "No memory for trans.\n"); + return "Failed to create call."; + } + + LOG_GCC(trans, LOGL_INFO, "VTY initiates call.\n"); + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_NET_SETUP, NULL); + + return NULL; +} + +/* Destroy a call from VTY command. */ +const char *vgcs_vty_terminate(struct gsm_network *gsmnet, struct gcr *gcr) +{ + enum trans_type trans_type; + uint32_t callref; + struct gsm_trans *trans; + + /* Get callref from stored suffix. Caller cannot choose a prefix. */ + trans_type = gcr->trans_type; + callref = atoi(gcr->group_id); + + /* Check if callref exists. */ + trans = trans_find_by_callref(gsmnet, trans_type, callref); + if (!trans) + return "Call does not exist."; + + LOG_GCC(trans, LOGL_INFO, "VTY terminates call.\n"); + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_NET_TERM, NULL); + + return NULL; +} + +/* + * BSS state machine - handles all BSS "call control" instances + */ + +static const struct value_string vgcs_bss_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_BSS_EV_SETUP), + OSMO_VALUE_STRING(VGCS_BSS_EV_SETUP_ACK), + OSMO_VALUE_STRING(VGCS_BSS_EV_SETUP_REFUSE), + OSMO_VALUE_STRING(VGCS_BSS_EV_ACTIVE_OR_FAIL), + OSMO_VALUE_STRING(VGCS_BSS_EV_UL_REQUEST), + OSMO_VALUE_STRING(VGCS_BSS_EV_UL_REQUEST_CNF), + OSMO_VALUE_STRING(VGCS_BSS_EV_UL_APP_DATA), + OSMO_VALUE_STRING(VGCS_BSS_EV_BSS_DTAP), + OSMO_VALUE_STRING(VGCS_BSS_EV_UL_RELEASE), + OSMO_VALUE_STRING(VGCS_BSS_EV_CLEAR), + OSMO_VALUE_STRING(VGCS_BSS_EV_CLOSE), + OSMO_VALUE_STRING(VGCS_BSS_EV_RELEASED), + { } +}; + +/* Blocks or unblocks uplinks of a BSS. */ +static int update_uplink_state(struct vgcs_bss *bss, bool uplink_busy) +{ + struct ran_msg ran_msg; + int rc; + + if (uplink_busy) { + /* Send UPLINK SEIZED COMMAND to BSS. */ + LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK SEIZED COMMAND towards BSS.\n"); + ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_UPLINK_SEIZED_CMD, + .uplink_seized_cmd = { + .cause = GSM0808_CAUSE_CALL_CONTROL, + }, + }; + } else { + /* Send UPLINK RELEASE COMMAND to BSS. */ + LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK RELEASE COMMAND towards BSS.\n"); + ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_UPLINK_RELEASE_CMD, + .uplink_release_cmd = { + .cause = GSM0808_CAUSE_CALL_CONTROL, + }, + }; + } + + rc = ran_encode_and_send(bss->fi, &ran_msg, bss->conn, false); + + return rc; +} + +/* Clear the connection towards BSS. + * The instance is removed soon, so it is detached from transaction and cells. */ +static void bss_clear(struct vgcs_bss *bss, uint8_t cause, bool notify_trans) +{ + struct ran_msg ran_msg; + struct gsm_trans *trans = bss->trans; + struct vgcs_bss_cell *cell, *cell2; + + /* Must detach us from transaction. */ + if (bss->trans) { + /* Remove pointer to talking BSS and cell. */ + if (bss == bss->trans->gcc.uplink_bss) { + bss->trans->gcc.uplink_bss = NULL; + bss->trans->gcc.uplink_cell = NULL; + } + llist_del(&bss->list); + bss->trans = NULL; + } + + /* Change state. */ + osmo_fsm_inst_state_chg(bss->fi, VGCS_BSS_ST_RELEASE, 0, 0); + + /* Send Clear Command to BSS. */ + ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_CLEAR_COMMAND, + .clear_command = { + .gsm0808_cause = cause, + }, + }; + if (bss->conn) { + LOG_BSS(bss, LOGL_DEBUG, "Sending CLEAR COMMAND for call controling channel.\n"); + ran_encode_and_send(bss->fi, &ran_msg, bss->conn, false); + } + + /* Trigger clear of all cells. Be safe, because the process will remove cells from list. */ + llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss) + osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_CLEAR, NULL); + + /* Detach us from all BSS, if still linked */ + llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss) { + llist_del(&cell->list_bss); + cell->bss = NULL; + } + + /* If all BS are gone, notify calling subscriber process. */ + if (notify_trans && trans && llist_empty(&trans->gcc.bss_list)) { + LOG_BSS(bss, LOGL_DEBUG, "Notify calling user process, that all BSSs are cleared.\n"); + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_BSS_RELEASED, NULL); + } +} + +/* When finally the BSS connection is released. (CLEAR COMPLETE response) + * The instance is removed, so it is detached from transaction and cells, if not already. */ +static void bss_destroy(struct vgcs_bss *bss) +{ + struct vgcs_bss_cell *cell, *cell2; + + LOG_BSS(bss, LOGL_DEBUG, "Removing BSS call controling instance.\n"); + + /* Must detach us from transaction, if not already. */ + if (bss->trans) { + /* Remove pointer to talking BSS and cell. */ + if (bss == bss->trans->gcc.uplink_bss) { + bss->trans->gcc.uplink_bss = NULL; + bss->trans->gcc.uplink_cell = NULL; + } + llist_del(&bss->list); + bss->trans = NULL; + } + + /* Detach us from RAN connection. */ + if (bss->conn) { + if (bss->conn->vgcs.bss == bss) + bss->conn->vgcs.bss = NULL; + if (bss->conn->vgcs.cell == bss) + bss->conn->vgcs.cell = NULL; + ran_conn_close(bss->conn); + bss->conn = NULL; + } + + /* Detach us from all BSS, if still linked */ + llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss) { + llist_del(&cell->list_bss); + cell->bss = NULL; + } + + /* Free FSM. (should be allocated) */ + osmo_fsm_inst_state_chg(bss->fi, VGCS_BSS_ST_NULL, 0, 0); + osmo_fsm_inst_term(bss->fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +/* Get identity of talker. + * This is required to detect if the talker is the calling subscriber. */ +static int talker_identity(struct vgcs_bss *bss, uint8_t *l3, int l3_len) +{ + struct osmo_mobile_identity mi; + int rc; + + rc = osmo_mobile_identity_decode_from_l3_buf(&mi, l3, l3_len, false); + if (rc < 0) { + LOG_BSS(bss, LOGL_DEBUG, "Talker's Identity cannot be decoded.\n"); + return rc; + } + + switch (mi.type) { + case GSM_MI_TYPE_IMSI: + if (!bss->trans->vsub) + break; + LOG_BSS(bss, LOGL_DEBUG, "Talker's sends IMSI %s, originator has IMSI %s.\n", + mi.imsi, bss->trans->vsub->imsi); + if (!strcmp(mi.imsi, bss->trans->vsub->imsi)) + return 1; + break; + case GSM_MI_TYPE_TMSI: + if (!bss->trans->vsub) + break; + LOG_BSS(bss, LOGL_DEBUG, "Talker's sends TMSI 0x%08x, originator has TMSI 0x%08x.\n", + mi.tmsi, bss->trans->vsub->tmsi); + if (mi.tmsi == bss->trans->vsub->tmsi) + return 1; + break; + default: + LOG_BSS(bss, LOGL_DEBUG, "Talker's Identity is not IMSI nor TMSI.\n"); + return -EINVAL; + } + + return 0; +} + +static void vgcs_bss_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss *bss = fi->priv; + struct ran_msg ran_msg; + + switch (event) { + case VGCS_BSS_EV_SETUP: + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_BSS_ST_SETUP, 0, 0); + /* Send VGCS/VBS SETUP to BSS. */ + LOG_BSS(bss, LOGL_DEBUG, "Sending VGCS/VBS SETUP towards BSS.\n"); + ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_VGCS_VBS_SETUP, + .vgcs_vbs_setup = { + .callref = { + .sf = (bss->trans->type == TRANS_GCC), + }, + .vgcs_feature_flags_present = true, + }, + }; + osmo_store32be_ext(bss->callref >> 3, &ran_msg.vgcs_vbs_setup.callref.call_ref_hi, 3); + ran_msg.vgcs_vbs_setup.callref.call_ref_lo = bss->callref & 0x7; + /* First message, so we must set "initial" to "true". */ + ran_encode_and_send(fi, &ran_msg, bss->conn, true); + break; + case VGCS_BSS_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n"); + bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_bss_fsm_setup(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss *bss = fi->priv; + struct vgcs_bss_cell *cell, *cell2; + + switch (event) { + case VGCS_BSS_EV_SETUP_ACK: + /* Receive VGCS/VBS SETUP ACK from BSS. */ + LOG_BSS(bss, LOGL_DEBUG, "Received VGCS/VBS SETUP ACK from BSS.\n"); + /* Send current uplink state to this BSS. */ + if (bss->trans) + update_uplink_state(bss, bss->trans->gcc.uplink_busy); + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_BSS_ST_ASSIGNMENT, 0, 0); + /* Trigger VGCS/VBS ASSIGNMENT */ + llist_for_each_entry_safe(cell, cell2, &bss->cell_list, list_bss) + osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_ASSIGN, NULL); + /* If all failed, clear call. */ + if (llist_empty(&bss->cell_list)) { + LOG_BSS(bss, LOGL_NOTICE, "All VGCS/VBS assignments failed.\n"); + bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true); + break; + } + break; + case VGCS_BSS_EV_SETUP_REFUSE: + /* Received VGCS/VBS SETUP REFUSE from BSS. */ + LOG_BSS(bss, LOGL_NOTICE, "Received VGCS/VBS SETUP REFUSE from BSS.\n"); + bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true); + break; + case VGCS_BSS_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n"); + bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false); + break; + case VGCS_BSS_EV_CLOSE: + /* The SCCP connection from the MSC has been closed. */ + LOG_BSS(bss, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n"); + if (bss->conn) { + bss->conn->vgcs.bss = NULL; + bss->conn = NULL; + } + bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_bss_fsm_assignment(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss *bss = fi->priv; + struct vgcs_bss_cell *c; + bool assigned; + + switch (event) { + case VGCS_BSS_EV_ACTIVE_OR_FAIL: + /* If all gone, clear call. */ + if (llist_empty(&bss->cell_list)) { + LOG_BSS(bss, LOGL_NOTICE, "All VGCS/VBS assignments failed.\n"); + bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true); + break; + } + /* Is there a response for all cells? + * This means that all the channels have a positive response + * There is no channel with negative response, because a + * negative response will remove the channel. */ + assigned = true; + llist_for_each_entry(c, &bss->cell_list, list_bss) { + if (!c->assigned) + assigned = false; + } + if (!assigned) + break; + LOG_BSS(bss, LOGL_DEBUG, "All VGCS/VBS assignments have responded.\n"); + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_BSS_ST_ACTIVE, 0, 0); + /* Notify calling subscriber process. */ + LOG_BSS(bss, LOGL_DEBUG, "Notify calling user process, that all BSSs are connected.\n"); + if (bss->trans) + osmo_fsm_inst_dispatch(bss->trans->gcc.fi, VGCS_GCC_EV_BSS_ESTABLISHED, NULL); + break; + case VGCS_BSS_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n"); + bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false); + break; + case VGCS_BSS_EV_CLOSE: + /* The SCCP connection from the MSC has been closed. */ + LOG_BSS(bss, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n"); + if (bss->conn) { + bss->conn->vgcs.bss = NULL; + bss->conn = NULL; + } + bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_bss_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss *bss = fi->priv, *other; + struct ran_msg *rx_ran_msg = data; + struct ran_msg tx_ran_msg; + int rc; + + switch (event) { + case VGCS_BSS_EV_UL_REQUEST: + LOG_BSS(bss, LOGL_DEBUG, "Listener changed to talker.\n"); + if (!bss->trans) + break; + /* Someone is talking. Check if there is no other uplink already busy. + * This should not happen, since all other cells are blocked (SEIZED) as soon as the uplink was + * requested. This may happen due to a race condition, where the uplink was requested before the + * UPLINK SEIZED COMMAND has been received by BSS. */ + if (bss->trans->gcc.uplink_busy) { + /* Send UPLINK REJECT COMMAND to BSS. */ + LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK REJECT COMMAND towards BSS.\n"); + tx_ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_UPLINK_REJECT_CMD, + .uplink_reject_cmd = { + .cause = GSM0808_CAUSE_CALL_CONTROL, + }, + }; + ran_encode_and_send(fi, &tx_ran_msg, bss->conn, false); + break; + } + /* Send UPLINK REQUEST ACKNOWLEDGE to BSS. */ + LOG_BSS(bss, LOGL_DEBUG, "Sending (VGCS) UPLINK REQUEST ACKNOWLEDGE towards BSS.\n"); + tx_ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_UPLINK_REQUEST_ACK, + }; + ran_encode_and_send(fi, &tx_ran_msg, bss->conn, false); + /* Set the busy flag and block all other cells. */ + bss->trans->gcc.uplink_bss = bss; + bss->trans->gcc.uplink_busy = true; + bss->trans->gcc.uplink_originator = false; + llist_for_each_entry(other, &bss->trans->gcc.bss_list, list) { + if (other == bss) + continue; + /* Update uplink state. */ + update_uplink_state(bss, bss->trans->gcc.uplink_busy); + } + /* Stop inactivity timer. */ + stop_inactivity_timer(bss->trans); + break; + case VGCS_BSS_EV_UL_REQUEST_CNF: + LOG_BSS(bss, LOGL_DEBUG, "Talker established uplink.\n"); + if (!bss->trans) + break; + if (!bss->trans->gcc.uplink_busy || bss->trans->gcc.uplink_bss != bss) { + LOG_BSS(bss, LOGL_ERROR, "Got UL REQUEST CNF, but we did not granted uplink.\n"); + break; + } + /* Determine if talker is the originator of the call. */ + rc = talker_identity(bss, rx_ran_msg->uplink_request_cnf.l3.l3, + rx_ran_msg->uplink_request_cnf.l3.l3_len); + if (rc > 0) { + bss->trans->gcc.uplink_originator = true; + LOG_BSS(bss, LOGL_DEBUG, "Talker is the originator of the call.\n"); + } + /* Set parameter. */ + set_parameter(bss->trans); + /* Set cell of current talker. */ + set_uplink_cell(bss, &rx_ran_msg->uplink_request_cnf.cell_identifier, 0); + /* Set MGW conference. */ + set_mgw_conference(bss->trans); + break; + case VGCS_BSS_EV_UL_APP_DATA: + LOG_BSS(bss, LOGL_DEBUG, "Talker sends application data on uplink.\n"); + if (!bss->trans) + break; + if (!bss->trans->gcc.uplink_busy || bss->trans->gcc.uplink_bss != bss) { + LOG_BSS(bss, LOGL_ERROR, "Got UP APP DATA, but we did not granted uplink.\n"); + break; + } + // FIXME: Use L3 info and feed to app. + break; + case VGCS_BSS_EV_BSS_DTAP: + LOG_BSS(bss, LOGL_DEBUG, "Talker sends DTAP message.\n"); + if (!bss->trans) + break; + if (!bss->trans->gcc.uplink_busy || bss->trans->gcc.uplink_bss != bss) { + LOG_BSS(bss, LOGL_ERROR, "Got DTAP from BSS, but we did not granted uplink.\n"); + break; + } + gsm44068_rcv_bcc_gcc(NULL, bss->trans, rx_ran_msg->dtap); + break; + case VGCS_BSS_EV_UL_RELEASE: + LOG_BSS(bss, LOGL_DEBUG, "Talker released uplink.\n"); + if (!bss->trans) + break; + if (bss->trans->type == TRANS_BCC) { + LOG_BSS(bss, LOGL_DEBUG, "This is a broadcast call, terminating call.\n"); + gcc_terminate_and_destroy(bss->trans, OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING); + break; + } + if (!bss->trans->gcc.uplink_busy) { + LOG_BSS(bss, LOGL_NOTICE, "Got uplink release, but no uplink busy.\n"); + break; + } + /* Talker release the uplink. Ignore, if not from the current talking cell. */ + if (bss->trans->gcc.uplink_bss != bss) { + LOG_BSS(bss, LOGL_NOTICE, "Got uplink release, but uplink busy in other cell.\n"); + break; + } + /* Clear the busy flag and unblock all other cells. */ + bss->trans->gcc.uplink_bss = NULL; + bss->trans->gcc.uplink_cell = NULL; + bss->trans->gcc.uplink_busy = false; + llist_for_each_entry(other, &bss->trans->gcc.bss_list, list) { + if (other == bss) + continue; + /* Update uplink state. */ + if (bss->trans) + update_uplink_state(bss, bss->trans->gcc.uplink_busy); + } + /* Set MGW conference. */ + set_mgw_conference(bss->trans); + /* Start inactivity timer. */ + start_inactivity_timer(bss->trans); + break; + case VGCS_BSS_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_BSS(bss, LOGL_DEBUG, "Received clearing from calling user process.\n"); + bss_clear(bss, GSM0808_CAUSE_CALL_CONTROL, false); + break; + case VGCS_BSS_EV_CLOSE: + /* The SCCP connection from the MSC has been closed. */ + LOG_BSS(bss, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n"); + if (bss->conn) { + bss->conn->vgcs.bss = NULL; + bss->conn = NULL; + } + bss_clear(bss, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC, true); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_bss_fsm_release(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss *bss = fi->priv; + + switch (event) { + case VGCS_BSS_EV_CLOSE: + /* The SCCP connection from the MSC has been closed while waitring fro CLEAR COMPLETE. */ + LOG_BSS(bss, LOGL_NOTICE, "Received SCCP closing collision.\n"); + bss_destroy(bss); + break; + case VGCS_BSS_EV_RELEASED: + LOG_BSS(bss, LOGL_DEBUG, "Received CLEAR COMPLETE from BSS, we are done!\n"); + bss_destroy(bss); + break; + default: + OSMO_ASSERT(false); + } +} + +static const struct osmo_fsm_state vgcs_bss_fsm_states[] = { + [VGCS_BSS_ST_NULL] = { + .name = "NULL", + .in_event_mask = S(VGCS_BSS_EV_SETUP) | + S(VGCS_BSS_EV_CLEAR), + .out_state_mask = S(VGCS_BSS_ST_SETUP), + .action = vgcs_bss_fsm_null, + }, + [VGCS_BSS_ST_SETUP] = { + .name = "SETUP sent", + .in_event_mask = S(VGCS_BSS_EV_SETUP_ACK) | + S(VGCS_BSS_EV_SETUP_REFUSE) | + S(VGCS_BSS_EV_CLEAR) | + S(VGCS_BSS_EV_CLOSE), + .out_state_mask = S(VGCS_BSS_ST_ASSIGNMENT) | + S(VGCS_BSS_ST_RELEASE), + .action = vgcs_bss_fsm_setup, + }, + [VGCS_BSS_ST_ASSIGNMENT] = { + .name = "ASSIGNMENT Sent", + .in_event_mask = S(VGCS_BSS_EV_ACTIVE_OR_FAIL) | + S(VGCS_BSS_EV_CLEAR) | + S(VGCS_BSS_EV_CLOSE), + .out_state_mask = S(VGCS_BSS_ST_ACTIVE) | + S(VGCS_BSS_ST_RELEASE), + .action = vgcs_bss_fsm_assignment, + }, + [VGCS_BSS_ST_ACTIVE] = { + .name = "VGCS/VBS Active", + .in_event_mask = S(VGCS_BSS_EV_UL_REQUEST) | + S(VGCS_BSS_EV_UL_REQUEST_CNF) | + S(VGCS_BSS_EV_UL_APP_DATA) | + S(VGCS_BSS_EV_BSS_DTAP) | + S(VGCS_BSS_EV_UL_RELEASE) | + S(VGCS_BSS_EV_CLEAR) | + S(VGCS_BSS_EV_CLOSE), + .out_state_mask = S(VGCS_BSS_ST_RELEASE), + .action = vgcs_bss_fsm_active, + }, + [VGCS_BSS_ST_RELEASE] = { + .name = "Releasing VGCS/VBS control", + .in_event_mask = S(VGCS_BSS_EV_CLEAR) | + S(VGCS_BSS_EV_RELEASED), + .out_state_mask = S(VGCS_BSS_ST_NULL), + .action = vgcs_bss_fsm_release, + }, +}; + +static struct osmo_fsm vgcs_bss_fsm = { + .name = "vgcs_bss", + .states = vgcs_bss_fsm_states, + .num_states = ARRAY_SIZE(vgcs_bss_fsm_states), + .log_subsys = DASCI, + .event_names = vgcs_bss_fsm_event_names, +}; + +/* The BSS accepts VGCS/VBS and sends us supported features. */ +void vgcs_vbs_setup_ack(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_SETUP_ACK, (void *)ran_msg); +} + +/* The BSS refuses VGCS/VBS. */ +void vgcs_vbs_setup_refuse(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_SETUP_REFUSE, (void *)ran_msg); +} + +/* The BSS needs more time for VGCS/VBS channel assignment. */ +void vgcs_vbs_queuing_ind(struct vgcs_bss_cell *cell) +{ + if (!cell->bss) + return; +} + +/* A mobile station requests the uplink on a VGCS channel. */ +void vgcs_uplink_request(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_REQUEST, (void *)ran_msg); +} + +/* The uplink on a VGCS channel has been established. */ +void vgcs_uplink_request_cnf(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_REQUEST_CNF, (void *)ran_msg); +} + +/* Application data received on the uplink of a VGCS channel. */ +void vgcs_app_data(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_APP_DATA, (void *)ran_msg); +} + +/* Application data received on the uplink of a VGCS channel. */ +void vgcs_bss_dtap(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_BSS_DTAP, (void *)ran_msg); +} + +/* A mobile station releases the uplink on a VGCS channel. */ +void vgcs_uplink_release_ind(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + if (!bss->trans) + return; + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_UL_RELEASE, (void *)ran_msg); +} + +/* The BSS gives cell status about VGCS/VBS channel. */ +void vgcs_vbs_assign_status(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg) +{ + if (!cell->bss) + return; +} + +void vgcs_vbs_caller_assign_cpl(struct gsm_trans *trans) +{ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_BSS_ASSIGN_CPL, NULL); +} + +void vgcs_vbs_caller_assign_fail(struct gsm_trans *trans) +{ + osmo_fsm_inst_dispatch(trans->gcc.fi, VGCS_GCC_EV_BSS_ASSIGN_FAIL, NULL); +} + +/* BSS indicated that the channel has been released. */ +void vgcs_vbs_clear_req(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_CLOSE, (void *)ran_msg); +} + +/* BSS indicated that the channel has been released. */ +void vgcs_vbs_clear_cpl(struct vgcs_bss *bss, const struct ran_msg *ran_msg) +{ + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_RELEASED, (void *)ran_msg); +} + +/* + * Cell resource state machine - handles all "resource control" instances + */ + +static const struct value_string vgcs_cell_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_CELL_EV_RTP_STREAM_GONE), + OSMO_VALUE_STRING(VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE), + OSMO_VALUE_STRING(VGCS_CELL_EV_RTP_STREAM_ESTABLISHED), + OSMO_VALUE_STRING(VGCS_CELL_EV_ASSIGN), + OSMO_VALUE_STRING(VGCS_CELL_EV_ASSIGN_RES), + OSMO_VALUE_STRING(VGCS_CELL_EV_ASSIGN_FAIL), + OSMO_VALUE_STRING(VGCS_CELL_EV_CLEAR), + OSMO_VALUE_STRING(VGCS_CELL_EV_CLOSE), + OSMO_VALUE_STRING(VGCS_CELL_EV_RELEASED), + { } +}; + +static void cell_destroy(struct vgcs_bss_cell *cell); + +/* Clear the connection towards BSS. + * Relations to the BSS and transaction is removed. */ +static void cell_clear(struct vgcs_bss_cell *cell, uint8_t cause) +{ + struct ran_msg ran_msg; + + /* Must detach us from BSS. */ + if (cell->bss) { + /* Remove pointer to talking channel. */ + if (cell->bss->trans && cell->bss->trans->gcc.uplink_cell == cell) + cell->bss->trans->gcc.uplink_cell = NULL; + llist_del(&cell->list_bss); + cell->bss = NULL; + } + + /* Change state. */ + if (cell->fi->state != VGCS_CELL_ST_RELEASE) + osmo_fsm_inst_state_chg(cell->fi, VGCS_CELL_ST_RELEASE, 0, 0); + + /* If there is no event to wait for, we can just destroy. */ + if (!cell->conn && !cell->rtps) { + cell_destroy(cell); + return; + } + + /* Send Clear Command to BSS. */ + if (cell->conn) { + ran_msg = (struct ran_msg){ + .msg_type = RAN_MSG_CLEAR_COMMAND, + .clear_command = { + .gsm0808_cause = cause, + }, + }; + LOG_CELL(cell, LOGL_DEBUG, "Sending CLEAR COMMAND for call controling channel.\n"); + ran_encode_and_send(cell->fi, &ran_msg, cell->conn, false); + } + + /* Clear RTP stream. This may trigger VGCS_CELL_EV_RTP_STREAM_GONE within this release function. */ + if (cell->rtps) + rtp_stream_release(cell->rtps); +} + +/* When finally the BSS connection is released. (CLEAR COMPLETE response) + * Relations to the BSS and transaction is removed, if not already. */ +static void cell_destroy(struct vgcs_bss_cell *cell) +{ + struct vgcs_mgw_ep *mgw; + + /* close RAN conn */ + if (cell->conn) { + cell->conn->vgcs.cell = NULL; + ran_conn_close(cell->conn); + cell->conn = NULL; + } + + /* Detach from BSS now. Check, to prevent race condition. */ + if (cell->bss) { + /* Remove pointer to talking channel. */ + if (cell->bss->trans && cell->bss->trans->gcc.uplink_cell == cell) + cell->bss->trans->gcc.uplink_cell = NULL; + llist_del(&cell->list_bss); + cell->bss = NULL; + } + + /* Detach from MGW now. Check, to prevent race condition. */ + if (cell->mgw) { + mgw = cell->mgw; + llist_del(&cell->list_mgw); + cell->mgw = NULL; + /* Destroy MGW endpoint, if list is empty. */ + if (llist_empty(&mgw->cell_list)) + osmo_fsm_inst_dispatch(mgw->fi, VGCS_MGW_EP_EV_CLEAR, NULL); + } + + LOG_CELL(cell, LOGL_DEBUG, "Detroy connection to cell.\n"); + + /* Free FSM. (should be allocated) */ + osmo_fsm_inst_state_chg(cell->fi, VGCS_CELL_ST_NULL, 0, 0); + osmo_fsm_inst_term(cell->fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +static void vgcs_cell_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss_cell *cell = fi->priv; + const struct codec_mapping *cm; + int rc; + + switch (event) { + case VGCS_CELL_EV_ASSIGN: + LOG_CELL(cell, LOGL_DEBUG, "Received assignment from BSS controling process.\n"); + /* Allocate rtps stream. */ + cell->rtps = rtp_stream_alloc(cell->fi, VGCS_CELL_EV_RTP_STREAM_GONE, + VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE, + VGCS_CELL_EV_RTP_STREAM_ESTABLISHED, RTP_TO_RAN, cell->call_id, + NULL); + if (!cell->rtps) { + LOG_CELL(cell, LOGL_DEBUG, "Failed to allocate RTP stream, cannot continue.\n"); + cell_destroy(cell); + break; + } + /* Hard coded codec: GSM V1 */ + cm = codec_mapping_by_gsm0808_speech_codec_type(GSM0808_SCT_FR1); + if (!cm) { + LOG_CELL(cell, LOGL_DEBUG, "Selected codec not supported, cannot continue.\n"); + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + } + rtp_stream_set_one_codec(cell->rtps, &cm->sdp); + /* Set initial mode. */ + rtp_stream_set_mode(cell->rtps, MGCP_CONN_RECV_ONLY); + /* Commit RTP stream. */ + if (!cell->bss || !cell->bss->trans) { + LOG_CELL(cell, LOGL_DEBUG, "No BSS/transaction, cannot continue.\n"); + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + } + if (!cell->mgw || !cell->mgw->mgw_ep) { + LOG_CELL(cell, LOGL_DEBUG, "No MGW endpoint, cannot continue.\n"); + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + } + rc = rtp_stream_ensure_ci(cell->rtps, cell->mgw->mgw_ep); + if (rc < 0) { + LOG_CELL(cell, LOGL_DEBUG, "Failed to trigger RTP stream CI.\n"); + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + } + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_CELL_ST_ASSIGNMENT, 0, 0); + break; + case VGCS_CELL_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_CELL(cell, LOGL_DEBUG, "Received clearing from BSS controling process.\n"); + cell_clear(cell, GSM0808_CAUSE_CALL_CONTROL); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_cell_fsm_assignment(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss_cell *cell = fi->priv; + struct ran_msg *rx_ran_msg = data; + struct ran_msg tx_ran_msg; + struct osmo_sockaddr_str ss; + const struct codec_mapping *cm; + struct vgcs_bss *bss; + int rc; + + switch (event) { + case VGCS_CELL_EV_RTP_STREAM_GONE: + /* The RTP stream failed. */ + LOG_CELL(cell, LOGL_ERROR, "RTP stream of MGW failed.\n"); + cell->rtps = NULL; + goto channel_fail; + break; + case VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE: + /* The RTP stream sends its peer. */ + if (!osmo_sockaddr_str_is_nonzero(&cell->rtps->local)) { + LOG_CELL(cell, LOGL_ERROR, "Invalid RTP address received from MGW: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(&cell->rtps->local)); + goto channel_fail; + } + LOG_CELL(cell, LOGL_DEBUG, + "MGW endpoint's RTP address available for the CI %s: " OSMO_SOCKADDR_STR_FMT " (osmux=%s:%d)\n", + rtp_direction_name(cell->rtps->dir), OSMO_SOCKADDR_STR_FMT_ARGS(&cell->rtps->local), + cell->rtps->use_osmux ? "yes" : "no", cell->rtps->local_osmux_cid); + /* Send VGCS/VBS ASSIGNMENT REQUEST to BSS */ + LOG_CELL(cell, LOGL_DEBUG, "Sending VGCS/VBS ASSIGNMENT REQUEST towards BSS.\n"); + tx_ran_msg = (struct ran_msg) { + .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_REQ, + .vgcs_vbs_assign_req = { + /* For now we support GSM/FR V1 only. This shall be supported by all MS. */ + .channel_type = { + .ch_indctr = GSM0808_CHAN_SPEECH, + .ch_rate_type = GSM0808_SPEECH_FULL_BM, + .perm_spch_len = 1, + .perm_spch[0] = GSM0808_PERM_FR1, + }, + /* For now we want a channel without any delay. */ + .ass_req = GSM0808_ASRQ_IMMEDIATE, + .callref = { + .sf = (cell->trans_type == TRANS_GCC), + }, + /* We need to identify the cell only. */ + .cell_identifier = { + .id_discr = CELL_IDENT_CI, + .id.ci = cell->cell_id, + }, + .aoip_transport_layer_present = true, + .call_id_present = true, + .call_id = cell->call_id, + .codec_list_present = true, + .codec_list_msc_preferred = { + .len = 1, + .codec[0] = { + .fi = 1, + .type = GSM0808_SCT_FR1, + .cfg = 0, + }, + }, + }, + }; + osmo_store32be_ext(cell->callref >> 3, &tx_ran_msg.vgcs_vbs_assign_req.callref.call_ref_hi, 3); + tx_ran_msg.vgcs_vbs_assign_req.callref.call_ref_lo = cell->callref & 0x7; + osmo_sockaddr_str_to_sockaddr(&cell->rtps->local, &tx_ran_msg.vgcs_vbs_assign_req.aoip_transport_layer); + /* First message, so we must set "initial" to "true". */ + ran_encode_and_send(fi, &tx_ran_msg, cell->conn, true); + break; + case VGCS_CELL_EV_RTP_STREAM_ESTABLISHED: + /* The RTP stream established. */ + LOG_CELL(cell, LOGL_DEBUG, "RTP stream is established.\n"); + break; + case VGCS_CELL_EV_ASSIGN_RES: + /* Receive VGCS/VBS ASSIGNMENT RESULT from BSS. */ + LOG_CELL(cell, LOGL_DEBUG, "Received VGCS/VBS ASSIGNMENT RESULT from BSS.\n"); + cell->assigned = true; + if (!rx_ran_msg->vgcs_vbs_assign_res.aoip_transport_layer_present + && !rx_ran_msg->vgcs_vbs_assign_res.codec_present + && !rx_ran_msg->vgcs_vbs_assign_res.call_id_present) { + LOG_CELL(cell, LOGL_ERROR, "Mandatory IEs missing.\n"); + goto channel_fail; + } + /* Send remote peer to RTP stream. */ + if (osmo_sockaddr_str_from_sockaddr(&ss, &rx_ran_msg->vgcs_vbs_assign_res.aoip_transport_layer)) { + LOG_CELL(cell, LOGL_ERROR, "Cannot RTP-CONNECT, invalid RTP IP:port in incoming MNCC " + "message\n"); + goto channel_fail; + } + rtp_stream_set_remote_addr(cell->rtps, &ss); + /* Send remote codec to RTP stream. */ + cm = codec_mapping_by_gsm0808_speech_codec_type(rx_ran_msg->vgcs_vbs_assign_res.codec_msc_chosen.type); + if (!cm) { + LOG_CELL(cell, LOGL_ERROR, "Chosen codec by BSC is not supported by MSC.\n"); + goto channel_fail; + } + rtp_stream_set_one_codec(cell->rtps, &cm->sdp); + /* Set listening mode. */ + rtp_stream_set_mode(cell->rtps, MGCP_CONN_SEND_ONLY); + /* Commit RTP stream. */ + rc = rtp_stream_commit(cell->rtps); + if (rc < 0) { + LOG_CELL(cell, LOGL_ERROR, "Failed to commit parameters to RTP stream.\n"); + goto channel_fail; + } + /* Change state. */ + osmo_fsm_inst_state_chg(fi, VGCS_CELL_ST_ACTIVE, 0, 0); + /* Notify BSS FSM about channel activation. */ + if (cell->bss) + osmo_fsm_inst_dispatch(cell->bss->fi, VGCS_BSS_EV_ACTIVE_OR_FAIL, NULL); + break; + case VGCS_CELL_EV_ASSIGN_FAIL: + /* Received VGCS/VBS ASSIGNMENT FAILURE from BSS. */ + LOG_CELL(cell, LOGL_NOTICE, "Received VGCS/VBS ASSIGNMENT FAILURE from BSS.\n"); +channel_fail: + bss = cell->bss; + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + /* Notify BSS FSM about channel failure. */ + if (bss) + osmo_fsm_inst_dispatch(bss->fi, VGCS_BSS_EV_ACTIVE_OR_FAIL, NULL); + break; + case VGCS_CELL_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_CELL(cell, LOGL_DEBUG, "Received clearing from BSS controling process.\n"); + cell_clear(cell, GSM0808_CAUSE_CALL_CONTROL); + break; + case VGCS_CELL_EV_CLOSE: + /* The SCCP connection from the MSC has been closed. */ + LOG_CELL(cell, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n"); + if (cell->conn) { + cell->conn->vgcs.bss = NULL; + cell->conn = NULL; + } + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_cell_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss_cell *cell = fi->priv; + + switch (event) { + case VGCS_CELL_EV_RTP_STREAM_GONE: + /* The RTP stream failed. */ + LOG_CELL(cell, LOGL_ERROR, "RTP stream of MGW failed.\n"); + cell->rtps = NULL; + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + case VGCS_CELL_EV_RTP_STREAM_ESTABLISHED: + /* The RTP stream established. */ + LOG_CELL(cell, LOGL_DEBUG, "RTP stream is established.\n"); + break; + case VGCS_CELL_EV_CLEAR: + /* The calling user process requested clearing of VGCS/VBS call. */ + LOG_CELL(cell, LOGL_DEBUG, "Received clearing from BSS controling process.\n"); + cell_clear(cell, GSM0808_CAUSE_CALL_CONTROL); + break; + case VGCS_CELL_EV_CLOSE: + /* The SCCP connection from the MSC has been closed. */ + LOG_CELL(cell, LOGL_NOTICE, "Received SCCP connecting closing from MSC.\n"); + if (cell->conn) { + cell->conn->vgcs.bss = NULL; + cell->conn = NULL; + } + cell_clear(cell, GSM0808_CAUSE_PROTOCOL_ERROR_BETWEEN_BSS_AND_MSC); + break; + default: + OSMO_ASSERT(false); + } +} + +static void vgcs_cell_fsm_release(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_bss_cell *cell = fi->priv; + + switch (event) { + case VGCS_CELL_EV_RTP_STREAM_GONE: + /* The RTP stream gone. */ + LOG_CELL(cell, LOGL_ERROR, "RTP stream gone.\n"); + cell->rtps = NULL; + /* Wait for RAN conn. */ + if (cell->conn) + break; + cell_destroy(cell); + break; + case VGCS_CELL_EV_CLEAR: + case VGCS_CELL_EV_RELEASED: + if (event == VGCS_CELL_EV_CLEAR) { + /* The SCCP connection from the MSC has been closed while waiting for CLEAR COMPLETE. */ + LOG_CELL(cell, LOGL_NOTICE, "Received SCCP closing collision.\n"); + } else + LOG_CELL(cell, LOGL_DEBUG, "Received CLEAR COMPLETE from BSS, we are done!\n"); + /* Wait for RTP stream. */ + if (cell->rtps) { + /* close RAN conn */ + if (cell->conn) { + cell->conn->vgcs.cell = NULL; + ran_conn_close(cell->conn); + cell->conn = NULL; + } + break; + } + cell_destroy(cell); + break; + default: + OSMO_ASSERT(false); + } +} + +static const struct osmo_fsm_state vgcs_cell_fsm_states[] = { + [VGCS_CELL_ST_NULL] = { + .name = "NULL", + .in_event_mask = S(VGCS_CELL_EV_ASSIGN) | + S(VGCS_CELL_EV_CLEAR), + .out_state_mask = S(VGCS_CELL_ST_ASSIGNMENT), + .action = vgcs_cell_fsm_null, + }, + [VGCS_CELL_ST_ASSIGNMENT] = { + .name = "ASSIGNMENT Sent", + .in_event_mask = S(VGCS_CELL_EV_RTP_STREAM_GONE) | + S(VGCS_CELL_EV_RTP_STREAM_ADDR_AVAILABLE) | + S(VGCS_CELL_EV_RTP_STREAM_ESTABLISHED) | + S(VGCS_CELL_EV_ASSIGN_RES) | + S(VGCS_CELL_EV_ASSIGN_FAIL) | + S(VGCS_CELL_EV_CLEAR) | + S(VGCS_CELL_EV_CLOSE), + .out_state_mask = S(VGCS_CELL_ST_ACTIVE) | + S(VGCS_CELL_ST_RELEASE), + .action = vgcs_cell_fsm_assignment, + }, + [VGCS_CELL_ST_ACTIVE] = { + .name = "VGCS/VBS channel active", + .in_event_mask = S(VGCS_CELL_EV_RTP_STREAM_GONE) | + S(VGCS_CELL_EV_RTP_STREAM_ESTABLISHED) | + S(VGCS_CELL_EV_CLEAR) | + S(VGCS_CELL_EV_CLOSE), + .out_state_mask = S(VGCS_CELL_ST_RELEASE), + .action = vgcs_cell_fsm_active, + }, + [VGCS_CELL_ST_RELEASE] = { + .name = "Releasing VGCS/VBS channel", + .in_event_mask = S(VGCS_CELL_EV_RTP_STREAM_GONE) | + S(VGCS_CELL_EV_CLEAR) | + S(VGCS_CELL_EV_RELEASED), + .out_state_mask = S(VGCS_CELL_ST_NULL), + .action = vgcs_cell_fsm_release, + }, +}; + +static struct osmo_fsm vgcs_cell_fsm = { + .name = "vgcs_cell", + .states = vgcs_cell_fsm_states, + .num_states = ARRAY_SIZE(vgcs_cell_fsm_states), + .log_subsys = DASCI, + .event_names = vgcs_cell_fsm_event_names, +}; + +/* The BSS accepts VGCS/VBS channel assignment. */ +void vgcs_vbs_assign_result(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg) +{ + osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_ASSIGN_RES, (void *)ran_msg); +} + +/* The BSS refuses VGCS/VBS channel assignment. */ +void vgcs_vbs_assign_fail(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg) +{ + osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_ASSIGN_FAIL, (void *)ran_msg); +} + +/* BSS indicated that the channel has been released. */ +void vgcs_vbs_clear_req_channel(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg) +{ + LOG_CELL(cell, LOGL_DEBUG, "Received CLEAR REQUEST for resource controling channel from BSS.\n"); + osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_CLOSE, (void *)ran_msg); +} + +/* BSS confirms the release of channel. */ +void vgcs_vbs_clear_cpl_channel(struct vgcs_bss_cell *cell, const struct ran_msg *ran_msg) +{ + LOG_CELL(cell, LOGL_DEBUG, "Received CLEAR COMPLETE for resource controling channel from BSS.\n"); + osmo_fsm_inst_dispatch(cell->fi, VGCS_CELL_EV_RELEASED, (void *)ran_msg); +} + +/* + * MGW endpoint FSM + */ + +static const struct value_string vgcs_mgw_ep_fsm_event_names[] = { + OSMO_VALUE_STRING(VGCS_MGW_EP_EV_FREE), + OSMO_VALUE_STRING(VGCS_MGW_EP_EV_CLEAR), + { } +}; + +static void vgcs_mgw_ep_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct vgcs_mgw_ep *mgw = fi->priv; + struct vgcs_bss_cell *cell, *cell2; + struct mgcp_client *mgcp_client; + + switch (event) { + case VGCS_MGW_EP_EV_FREE: + LOGP(DASCI, LOGL_DEBUG, "MGW connection closed, removing all cell instances.\n"); + llist_for_each_entry_safe(cell, cell2, &mgw->cell_list, list_mgw) { + if (cell->rtps) + cell->rtps->ci = NULL; + llist_del(&cell->list_mgw); + cell->mgw = NULL; + } + /* Put MGCP client back into MGW pool. */ + mgcp_client = osmo_mgcpc_ep_client(mgw->mgw_ep); + mgcp_client_pool_put(mgcp_client); + /* Destroy this instance. */ + osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + case VGCS_MGW_EP_EV_CLEAR: + if (!llist_empty(&mgw->cell_list)) + break; + LOGP(DASCI, LOGL_DEBUG, "Cell list of MGW instance is now empty, dropping.\n"); + /* Destroy this instance. */ + osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + default: + OSMO_ASSERT(false); + } +} + +static const struct osmo_fsm_state vgcs_mgw_ep_fsm_states[] = { + [VGCS_MGW_EP_ST_NULL] = { + .name = "NULL", + .out_state_mask = S(VGCS_MGW_EP_ST_ACTIVE), + }, + [VGCS_MGW_EP_ST_ACTIVE] = { + .name = "MGW endpoint allocated", + .in_event_mask = S(VGCS_MGW_EP_EV_FREE) | + S(VGCS_MGW_EP_EV_CLEAR), + .out_state_mask = S(VGCS_MGW_EP_ST_NULL), + .action = vgcs_mgw_ep_fsm_active, + }, +}; + +static struct osmo_fsm vgcs_mgw_ep_fsm = { + .name = "vgcs_mgw_ep", + .states = vgcs_mgw_ep_fsm_states, + .num_states = ARRAY_SIZE(vgcs_mgw_ep_fsm_states), + .log_subsys = DASCI, + .event_names = vgcs_mgw_ep_fsm_event_names, +}; diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c index 51504ef51..1f389f455 100644 --- a/src/libmsc/msc_vty.c +++ b/src/libmsc/msc_vty.c @@ -1,5 +1,5 @@ /* MSC interface to quagga VTY */ -/* (C) 2016-2018 by sysmocom s.m.f.c. GmbH <info@sysmocom.de> +/* (C) 2016-2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c) * (C) 2009-2017 by Harald Welte <laforge@gnumonks.org> * (C) 2009-2011 by Holger Hans Peter Freyther @@ -33,9 +33,11 @@ #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/protocol/gsm_04_14.h> #include <osmocom/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/gsm23236.h> #include <osmocom/sigtran/sccp_helpers.h> +#include <osmocom/vty/tdef_vty.h> #include <osmocom/vty/command.h> #include <osmocom/vty/logging.h> #include <osmocom/vty/misc.h> @@ -65,10 +67,12 @@ #include <osmocom/msc/sgs_vty.h> #include <osmocom/msc/sccp_ran.h> #include <osmocom/msc/ran_peer.h> +#include <osmocom/msc/ran_infra.h> +#include <osmocom/msc/asci_vty.h> static struct gsm_network *gsmnet = NULL; -struct cmd_node net_node = { +static struct cmd_node net_node = { GSMNET_NODE, "%s(config-net)# ", 1, @@ -128,19 +132,23 @@ DEFUN(cfg_net_mnc, DEFUN(cfg_net_name_short, cfg_net_name_short_cmd, - "short name NAME", + "short name .NAME", "Set the short GSM network name\n" NAME_CMD_STR NAME_STR) { - osmo_talloc_replace_string(gsmnet, &gsmnet->name_short, argv[0]); + if (gsmnet->name_short != NULL) + talloc_free(gsmnet->name_short); + gsmnet->name_short = argv_concat(argv, argc, 0); return CMD_SUCCESS; } DEFUN(cfg_net_name_long, cfg_net_name_long_cmd, - "long name NAME", + "long name .NAME", "Set the long GSM network name\n" NAME_CMD_STR NAME_STR) { - osmo_talloc_replace_string(gsmnet, &gsmnet->name_long, argv[0]); + if (gsmnet->name_long != NULL) + talloc_free(gsmnet->name_long); + gsmnet->name_long = argv_concat(argv, argc, 0); return CMD_SUCCESS; } @@ -148,12 +156,13 @@ DEFUN(cfg_net_name_long, DEFUN(cfg_net_encryption, cfg_net_encryption_cmd, - "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]", + "encryption a5 <0-4> [<0-4>] [<0-4>] [<0-4>] [<0-4>]", ENCRYPTION_STR "GSM A5 Air Interface Encryption.\n" "A5/n Algorithm Number\n" "A5/n Algorithm Number\n" "A5/n Algorithm Number\n" + "A5/n Algorithm Number\n" "A5/n Algorithm Number\n") { unsigned int i; @@ -165,41 +174,21 @@ DEFUN(cfg_net_encryption, return CMD_SUCCESS; } -/* So far just a boolean switch, a future patch might add individual config for UEA1 and UEA2, see OS#4143 */ DEFUN(cfg_net_encryption_uea, cfg_net_encryption_uea_cmd, "encryption uea <0-2> [<0-2>] [<0-2>]", ENCRYPTION_STR - "UTRAN (3G) encryption algorithms to allow: 0 = UEA0 (no encryption), 1 = UEA1, 2 = UEA2." - " NOTE: the current implementation does not allow free choice of combining encryption algorithms yet." - " The only valid settings are either 'encryption uea 0' or 'encryption uea 1 2'.\n" + "UTRAN (3G) encryption algorithms to allow: 0 = UEA0 (no encryption), 1 = UEA1, 2 = UEA2.\n" "UEAn Algorithm Number\n" "UEAn Algorithm Number\n" "UEAn Algorithm Number\n" ) { unsigned int i; - uint8_t mask = 0; + gsmnet->uea_encryption_mask = 0; for (i = 0; i < argc; i++) - mask |= (1 << atoi(argv[i])); - - if (mask == (1 << 0)) { - /* UEA0. Disable encryption. */ - gsmnet->uea_encryption = false; - } else if (mask == ((1 << 1) | (1 << 2))) { - /* UEA1 and UEA2. Enable encryption. */ - gsmnet->uea_encryption = true; - } else { - vty_out(vty, - "%% Error: the current implementation does not allow free choice of combining%s" - "%% encryption algorithms yet. The only valid settings are either%s" - "%% encryption uea 0%s" - "%% or%s" - "%% encryption uea 1 2%s", - VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; - } + gsmnet->uea_encryption_mask |= (1 << atoi(argv[i])); return CMD_SUCCESS; } @@ -302,32 +291,44 @@ DEFUN(cfg_net_no_timezone, return CMD_SUCCESS; } -DEFUN(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd, - "periodic location update <6-1530>", - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval in Minutes\n") +/* NOTE: actually this is subscriber expiration timeout */ +#define PER_LOC_UPD_STR "Periodic Location Updating Interval\n" + +DEFUN_DEPRECATED(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd, + "periodic location update <6-1530>", + PER_LOC_UPD_STR PER_LOC_UPD_STR PER_LOC_UPD_STR + "Periodic Location Updating Interval in Minutes\n") { - struct gsm_network *net = vty->index; + int minutes = atoi(argv[0]); + int rc; - net->t3212 = atoi(argv[0]) / 6; + vty_out(vty, "%% 'periodic location update' is now deprecated. " + "Use 'msc' / 'timer vlr T3212' to change subscriber expiration " + "timeout.%s", VTY_NEWLINE); - return CMD_SUCCESS; + /* We used to double this value and add a minute when scheduling the + * expiration timer. Let's emulate the old behaviour here. */ + minutes = minutes * 2 + 1; + vty_out(vty, "%% Setting T3212 to %d minutes " + "(emulating the old behaviour).%s", + minutes, VTY_NEWLINE); + + rc = osmo_tdef_set(msc_tdefs_vlr, 3212, minutes, OSMO_TDEF_M); + return rc ? CMD_WARNING : CMD_SUCCESS; } -DEFUN(cfg_net_no_per_loc_upd, cfg_net_no_per_loc_upd_cmd, - "no periodic location update", - NO_STR - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n" - "Periodic Location Updating Interval\n") +DEFUN_DEPRECATED(cfg_net_no_per_loc_upd, cfg_net_no_per_loc_upd_cmd, + "no periodic location update", + NO_STR PER_LOC_UPD_STR PER_LOC_UPD_STR PER_LOC_UPD_STR) { - struct gsm_network *net = vty->index; + int rc; - net->t3212 = 0; + vty_out(vty, "%% 'periodic location update' is now deprecated: " + "use 'timer T3212' to change subscriber expiration " + "timeout.%s", VTY_NEWLINE); - return CMD_SUCCESS; + rc = osmo_tdef_set(msc_tdefs_vlr, 3212, 0, OSMO_TDEF_M); + return rc ? CMD_WARNING : CMD_SUCCESS; } DEFUN(cfg_net_call_wait, cfg_net_call_wait_cmd, @@ -370,10 +371,12 @@ static int config_write_net(struct vty *vty) } vty_out(vty, "%s", VTY_NEWLINE); - if (!gsmnet->uea_encryption) - vty_out(vty, " encryption uea 0%s", VTY_NEWLINE); - else - vty_out(vty, " encryption uea 1 2%s", VTY_NEWLINE); + vty_out(vty, " encryption uea"); + for (i = 0; i < 8; i++) { + if (gsmnet->uea_encryption_mask & (1 << i)) + vty_out(vty, " %u", i); + } + vty_out(vty, "%s", VTY_NEWLINE); vty_out(vty, " authentication %s%s", gsmnet->authentication_required ? "required" : "optional", VTY_NEWLINE); vty_out(vty, " rrlp mode %s%s", msc_rrlp_mode_name(gsmnet->rrlp.mode), @@ -388,20 +391,12 @@ static int config_write_net(struct vty *vty) vty_out(vty, " timezone %d %d%s", gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE); } - if (gsmnet->t3212 == 0) - vty_out(vty, " no periodic location update%s", VTY_NEWLINE); - else - vty_out(vty, " periodic location update %u%s", - gsmnet->t3212 * 6, VTY_NEWLINE); - - if (gsmnet->emergency.route_to_msisdn) { - vty_out(vty, " emergency-call route-to-msisdn %s%s", - gsmnet->emergency.route_to_msisdn, VTY_NEWLINE); - } if (!gsmnet->call_waiting) vty_out(vty, " no call-waiting%s", VTY_NEWLINE); + mgcp_client_pool_config_write(vty, " "); + return CMD_SUCCESS; } @@ -422,6 +417,15 @@ DEFUN(cfg_msc, cfg_msc_cmd, #define MNCC_GUARD_TIMEOUT_STR "Set global guard timer for mncc interface activity\n" #define MNCC_GUARD_TIMEOUT_VALUE_STR "guard timer value (sec.)\n" +DEFUN_DEPRECATED(cfg_sms_database, cfg_sms_database_cmd, + "sms-database PATH", + "Set the path to the MSC-SMS database file\n" + "Relative or absolute file system path to the database file (default is '" SMS_DEFAULT_DB_FILE_PATH "')\n") +{ + osmo_talloc_replace_string(gsmnet, &gsmnet->sms_queue_cfg->db_file_path, argv[0]); + return CMD_SUCCESS; +} + DEFUN(cfg_msc_mncc_internal, cfg_msc_mncc_internal_cmd, "mncc internal", @@ -484,6 +488,24 @@ DEFUN(cfg_msc_no_assign_tmsi, cfg_msc_no_assign_tmsi_cmd, return CMD_SUCCESS; } +DEFUN_ATTR(cfg_msc_lcls_disable, cfg_msc_lcls_disable_cmd, + "lcls-permitted", + "Globally allow LCLS (Local Call Local Switch) for all calls on this MSC.\n", + CMD_ATTR_IMMEDIATE) +{ + gsmnet->lcls_permitted = true; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_msc_no_lcls_disable, cfg_msc_no_lcls_disable_cmd, + "no lcls-permitted", + NO_STR "Globally disable LCLS (Local Call Local Switch) for all calls on this MSC.\n", + CMD_ATTR_IMMEDIATE) +{ + gsmnet->lcls_permitted = false; + return CMD_SUCCESS; +} + DEFUN(cfg_msc_cs7_instance_a, cfg_msc_cs7_instance_a_cmd, "cs7-instance-a <0-15>", @@ -549,7 +571,7 @@ DEFUN(cfg_msc_check_imei_rqd, cfg_msc_check_imei_rqd_cmd, return CMD_SUCCESS; } -DEFUN(cfg_msc_paging_response_timer, cfg_msc_paging_response_timer_cmd, +DEFUN_DEPRECATED(cfg_msc_paging_response_timer, cfg_msc_paging_response_timer_cmd, "paging response-timer (default|<1-65535>)", "Configure Paging\n" "Set Paging timeout, the minimum time to pass between (unsuccessful) Pagings sent towards" @@ -557,10 +579,22 @@ DEFUN(cfg_msc_paging_response_timer, cfg_msc_paging_response_timer_cmd, "Set to default timeout (" OSMO_STRINGIFY_VAL(MSC_PAGING_RESPONSE_TIMER_DEFAULT) " seconds)\n" "Set paging timeout in seconds\n") { + int rat; + int paging_response_timer; if (!strcmp(argv[0], "default")) - gsmnet->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT; + paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT; else - gsmnet->paging_response_timer = atoi(argv[0]); + paging_response_timer = atoi(argv[0]); + + for (rat = 0; rat < OSMO_RAT_COUNT; rat++) { + osmo_tdef_set(msc_ran_infra[rat].tdefs, -4, paging_response_timer, OSMO_TDEF_S); + } + + vty_out(vty, "%% paging response-timer is deprecated.%s" + "%% All ran timer has been modified.%s" + "%% use 'timer <geran|utran|sgs> X4 %s' instead%s", + VTY_NEWLINE, VTY_NEWLINE, argv[0], VTY_NEWLINE); + return CMD_SUCCESS; } @@ -654,17 +688,91 @@ DEFUN(cfg_msc_osmux, return CMD_SUCCESS; } +#define NRI_STR "Mapping of Network Resource Indicators to this MSC, for MSC pooling\n" +DEFUN(cfg_msc_nri_bitlen, cfg_msc_nri_bitlen_cmd, + "nri bitlen <0-15>", + NRI_STR + "Set number of NRI bits to place in TMSI identities (always starting just after the most significant octet)\n" + "bit count (default: " OSMO_STRINGIFY_VAL(NRI_BITLEN_DEFAULT) ")\n") +{ + gsmnet->vlr->cfg.nri_bitlen = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define NRI_STR "Mapping of Network Resource Indicators to this MSC, for MSC pooling\n" +#define NRI_ARGS_TO_STR_FMT "%s%s%s" +#define NRI_ARGS_TO_STR_ARGS(ARGC, ARGV) ARGV[0], (ARGC>1)? ".." : "", (ARGC>1)? ARGV[1] : "" +#define NRI_FIRST_LAST_STR "First value of the NRI value range, should not surpass the configured 'nri bitlen'.\n" \ + "Last value of the NRI value range, should not surpass the configured 'nri bitlen' and be larger than the" \ + " first value; if omitted, apply only the first value.\n" + +DEFUN(cfg_msc_nri_add, cfg_msc_nri_add_cmd, + "nri add <0-32767> [<0-32767>]", + NRI_STR "Add NRI value or range to the NRI mapping for this MSC\n" + NRI_FIRST_LAST_STR) +{ + const char *message; + int rc = osmo_nri_ranges_vty_add(&message, NULL, gsmnet->vlr->cfg.nri_ranges, argc, argv, gsmnet->vlr->cfg.nri_bitlen); + if (message) { + vty_out(vty, "%% %s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv)); + } + if (rc < 0) + return CMD_WARNING; + return CMD_SUCCESS; +} + +DEFUN(cfg_msc_nri_del, cfg_msc_nri_del_cmd, + "nri del <0-32767> [<0-32767>]", + NRI_STR "Remove NRI value or range from the NRI mapping for this MSC\n" + NRI_FIRST_LAST_STR) +{ + const char *message; + int rc = osmo_nri_ranges_vty_del(&message, NULL, gsmnet->vlr->cfg.nri_ranges, argc, argv); + if (message) { + vty_out(vty, "%% %s: " NRI_ARGS_TO_STR_FMT, message, NRI_ARGS_TO_STR_ARGS(argc, argv)); + } + if (rc < 0) + return CMD_WARNING; + return CMD_SUCCESS; +} + +static void msc_write_nri(struct vty *vty) +{ + struct osmo_nri_range *r; + + llist_for_each_entry(r, &gsmnet->vlr->cfg.nri_ranges->entries, entry) { + if (osmo_nri_range_validate(r, 255)) + vty_out(vty, " %% INVALID RANGE:"); + vty_out(vty, " nri add %d", r->first); + if (r->first != r->last) + vty_out(vty, " %d", r->last); + vty_out(vty, "%s", VTY_NEWLINE); + } +} + +DEFUN(show_nri, show_nri_cmd, + "show nri", + SHOW_STR NRI_STR) +{ + msc_write_nri(vty); + return CMD_SUCCESS; +} + static int config_write_msc(struct vty *vty) { vty_out(vty, "msc%s", VTY_NEWLINE); if (gsmnet->mncc_sock_path) vty_out(vty, " mncc external %s%s", gsmnet->mncc_sock_path, VTY_NEWLINE); + else + vty_out(vty, " mncc internal%s", VTY_NEWLINE); vty_out(vty, " mncc guard-timeout %i%s", gsmnet->mncc_guard_timeout, VTY_NEWLINE); vty_out(vty, " ncss guard-timeout %i%s", gsmnet->ncss_guard_timeout, VTY_NEWLINE); vty_out(vty, " %sassign-tmsi%s", gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE); + if (gsmnet->lcls_permitted) + vty_out(vty, " lcls-permitted%s", VTY_NEWLINE); vty_out(vty, " cs7-instance-a %u%s", gsmnet->a.cs7_instance, VTY_NEWLINE); @@ -688,9 +796,6 @@ static int config_write_msc(struct vty *vty) vty_out(vty, " check-imei-rqd 1%s", VTY_NEWLINE); } - if (gsmnet->paging_response_timer != MSC_PAGING_RESPONSE_TIMER_DEFAULT) - vty_out(vty, " paging response-timer %u%s", gsmnet->paging_response_timer, VTY_NEWLINE); - if (gsmnet->emergency.route_to_msisdn) { vty_out(vty, " emergency-call route-to-msisdn %s%s", gsmnet->emergency.route_to_msisdn, VTY_NEWLINE); @@ -716,6 +821,11 @@ static int config_write_msc(struct vty *vty) neighbor_ident_vty_write(vty); + /* Timer introspection commands (generic osmo_tdef API) */ + osmo_tdef_vty_groups_write(vty, " "); + + msc_write_nri(vty); + return CMD_SUCCESS; } @@ -832,7 +942,7 @@ static void vty_dump_one_conn(struct vty *vty, const struct msub *msub, if (vsub) { MSC_VTY_DUMP(vty, offset, "LAC / cell ID: %u / %u%s", - vsub->cgi.lai.lac, vsub->cgi.cell_identity, + msc_a->via_cell.lai.lac, msc_a->via_cell.cell_identity, VTY_NEWLINE); } @@ -865,7 +975,6 @@ static void vty_dump_one_conn(struct vty *vty, const struct msub *msub, static void vty_dump_one_subscr(struct vty *vty, struct vlr_subscr *vsub, int offset, uint8_t dump_flags) { - struct gsm_network *net; struct timespec now; char buf[128]; @@ -939,9 +1048,7 @@ static void vty_dump_one_subscr(struct vty *vty, struct vlr_subscr *vsub, VTY_NEWLINE); } - /* XXX move t3212 into struct vlr_instance? */ - net = vsub->vlr->user_ctx; - if (!net->t3212) { + if (!vlr_timer(vsub->vlr, 3212)) { MSC_VTY_DUMP(vty, offset, "Expires: never (T3212 is disabled)%s", VTY_NEWLINE); } else if (vsub->expire_lu == VLR_SUBSCRIBER_NO_EXPIRATION) { @@ -1148,6 +1255,11 @@ static int _send_sms_str(struct vlr_subscr *receiver, struct gsm_sms *sms; sms = sms_from_text(receiver, sender_msisdn, 0, str); + if (!sms) { + LOGP(DLSMS, LOGL_ERROR, "Failed to allocate SMS\n"); + return CMD_WARNING; + } + sms->protocol_id = tp_pid; /* store in database for the queue */ @@ -1217,13 +1329,12 @@ DEFUN(show_subscr, show_subscr_cmd, return CMD_SUCCESS; } -DEFUN(subscriber_create, - subscriber_create_cmd, - "subscriber create imsi ID", - "Operations on a Subscriber\n" \ - "Create new subscriber\n" \ - "Identify the subscriber by his IMSI\n" \ - "Identifier for the subscriber\n") +DEFUN_DEPRECATED(subscriber_create, subscriber_create_cmd, + "subscriber create imsi ID", + "Operations on a Subscriber\n" + "Create new subscriber\n" + "Identify the subscriber by his IMSI\n" + "Identifier for the subscriber\n") { vty_out(vty, "%% 'subscriber create' now needs to be done at osmo-hlr%s", VTY_NEWLINE); @@ -1624,6 +1735,7 @@ DEFUN(subscriber_mstest_close, gsm0414_tx_close_tch_loop_cmd(msc_a, loop_mode); + vlr_subscr_put(vsub, VSUB_USE_VTY); return CMD_SUCCESS; } @@ -1652,6 +1764,7 @@ DEFUN(subscriber_mstest_open, gsm0414_tx_open_loop_cmd(msc_a); + vlr_subscr_put(vsub, VSUB_USE_VTY); return CMD_SUCCESS; } @@ -1711,94 +1824,49 @@ DEFUN(show_stats, SHOW_STR "Display network statistics\n") { vty_out(vty, "Location Update : %" PRIu64 " attach, %" PRIu64 " normal, %" PRIu64 " periodic%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_ATTACH)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_NORMAL)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_PERIODIC)->current, VTY_NEWLINE); vty_out(vty, "IMSI Detach Indications : %" PRIu64 "%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_LOC_UPDATE_TYPE_DETACH)->current, VTY_NEWLINE); vty_out(vty, "Location Updating Results: %" PRIu64 " completed, %" PRIu64 " failed%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_LOC_UPDATE_COMPLETED)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_LOC_UPDATE_FAILED)->current, VTY_NEWLINE); vty_out(vty, "SMS MO : %" PRIu64 " submitted, %" PRIu64 " no receiver%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_SMS_SUBMITTED].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_SMS_SUBMITTED)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_SMS_NO_RECEIVER)->current, VTY_NEWLINE); vty_out(vty, "SMS MT : %" PRIu64 " delivered, %" PRIu64 " no memory, %" PRIu64 " other error%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_SMS_DELIVERED].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_MEM].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_SMS_RP_ERR_OTHER].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_SMS_DELIVERED)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_SMS_RP_ERR_MEM)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_SMS_RP_ERR_OTHER)->current, VTY_NEWLINE); vty_out(vty, "MO Calls : %" PRIu64 " setup, %" PRIu64 " connect ack%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MO_CONNECT_ACK].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_CALL_MO_SETUP)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_CALL_MO_CONNECT_ACK)->current, VTY_NEWLINE); vty_out(vty, "MT Calls : %" PRIu64 " setup, %" PRIu64 " connect%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MT_SETUP].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_CALL_MT_CONNECT].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_CALL_MT_SETUP)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_CALL_MT_CONNECT)->current, VTY_NEWLINE); vty_out(vty, "MO NC SS/USSD : %" PRIu64 " requests, %" PRIu64 " established, %" PRIu64 " rejected%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_REQUESTS].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_ESTABLISHED].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_REQUESTS].current - - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MO_ESTABLISHED].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MO_REQUESTS)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MO_ESTABLISHED)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MO_REQUESTS)->current + - rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MO_ESTABLISHED)->current, VTY_NEWLINE); vty_out(vty, "MT NC SS/USSD : %" PRIu64 " requests, %" PRIu64 " established, %" PRIu64 " rejected%s", - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_REQUESTS].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED].current, - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_REQUESTS].current - - gsmnet->msc_ctrs->ctr[MSC_CTR_NC_SS_MT_ESTABLISHED].current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MT_REQUESTS)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MT_ESTABLISHED)->current, + rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MT_REQUESTS)->current + - rate_ctr_group_get_ctr(gsmnet->msc_ctrs, MSC_CTR_NC_SS_MT_ESTABLISHED)->current, VTY_NEWLINE); return CMD_SUCCESS; } -DEFUN(show_smsqueue, - show_smsqueue_cmd, - "show sms-queue", - SHOW_STR "Display SMSqueue statistics\n") -{ - sms_queue_stats(gsmnet->sms_queue, vty); - return CMD_SUCCESS; -} - -DEFUN(smsqueue_trigger, - smsqueue_trigger_cmd, - "sms-queue trigger", - "SMS Queue\n" "Trigger sending messages\n") -{ - sms_queue_trigger(gsmnet->sms_queue); - return CMD_SUCCESS; -} - -DEFUN(smsqueue_max, - smsqueue_max_cmd, - "sms-queue max-pending <1-500>", - "SMS Queue\n" "SMS to deliver in parallel\n" "Amount\n") -{ - sms_queue_set_max_pending(gsmnet->sms_queue, atoi(argv[0])); - return CMD_SUCCESS; -} - -DEFUN(smsqueue_clear, - smsqueue_clear_cmd, - "sms-queue clear", - "SMS Queue\n" "Clear the queue of pending SMS\n") -{ - sms_queue_clear(gsmnet->sms_queue); - return CMD_SUCCESS; -} - -DEFUN(smsqueue_fail, - smsqueue_fail_cmd, - "sms-queue max-failure <1-500>", - "SMS Queue\n" "Maximum amount of delivery failures\n" "Amount\n") -{ - sms_queue_set_max_failure(gsmnet->sms_queue, atoi(argv[0])); - return CMD_SUCCESS; -} - DEFUN(cfg_mncc_int, cfg_mncc_int_cmd, "mncc-int", "Configure internal MNCC handler") @@ -1925,11 +1993,11 @@ DEFUN(cfg_hlr_ipa_name, "Set the IPA name of this MSC\n" "A unique name for this MSC. For example: PLMN + redundancy server number: MSC-901-70-0. " "This name is used for GSUP routing and must be set if more than one MSC is connected to the HLR. " - "The default is 'MSC-00-00-00-00-00-00'.\n") + "The default is 'unnamed-MSC'.\n") { if (vty->type != VTY_FILE) { vty_out(vty, "The IPA name cannot be changed at run-time; " - "It can only be set in the configuraton file.%s", VTY_NEWLINE); + "It can only be set in the configuration file.%s", VTY_NEWLINE); return CMD_WARNING; } @@ -1974,10 +2042,15 @@ void msc_vty_init(struct gsm_network *msc_network) install_element(GSMNET_NODE, &cfg_net_no_per_loc_upd_cmd); install_element(GSMNET_NODE, &cfg_net_call_wait_cmd); install_element(GSMNET_NODE, &cfg_net_no_call_wait_cmd); + mgcp_client_pool_vty_init(GSMNET_NODE, MGW_NODE, NULL, msc_network->mgw.mgw_pool); + install_element(CONFIG_NODE, &cfg_msc_cmd); install_node(&msc_node, config_write_msc); + install_element(MSC_NODE, &cfg_sms_database_cmd); install_element(MSC_NODE, &cfg_msc_assign_tmsi_cmd); + install_element(MSC_NODE, &cfg_msc_lcls_disable_cmd); + install_element(MSC_NODE, &cfg_msc_no_lcls_disable_cmd); install_element(MSC_NODE, &cfg_msc_mncc_internal_cmd); install_element(MSC_NODE, &cfg_msc_mncc_external_cmd); install_element(MSC_NODE, &cfg_msc_mncc_guard_timeout_cmd); @@ -1995,14 +2068,24 @@ void msc_vty_init(struct gsm_network *msc_network) install_element(MSC_NODE, &cfg_msc_no_sms_over_gsup_cmd); install_element(MSC_NODE, &cfg_msc_osmux_cmd); install_element(MSC_NODE, &cfg_msc_handover_number_range_cmd); + install_element(MSC_NODE, &cfg_msc_nri_bitlen_cmd); + install_element(MSC_NODE, &cfg_msc_nri_add_cmd); + install_element(MSC_NODE, &cfg_msc_nri_del_cmd); neighbor_ident_vty_init(msc_network); - mgcp_client_vty_init(msc_network, MSC_NODE, &msc_network->mgw.conf); + /* Timer configuration commands (generic osmo_tdef API) */ + osmo_tdef_vty_groups_init(MSC_NODE, msc_tdef_group); + + /* Deprecated: Old MGCP config without pooling support in MSC node: */ + mgcp_client_vty_init(msc_network, MSC_NODE, msc_network->mgw.conf); + #ifdef BUILD_IU ranap_iu_vty_init(MSC_NODE, (enum ranap_nsap_addr_enc*)&msc_network->iu.rab_assign_addr_enc); #endif sgs_vty_init(); + smsc_vty_init(msc_network); + asci_vty_init(msc_network); osmo_fsm_vty_add_cmds(); @@ -2013,6 +2096,7 @@ void msc_vty_init(struct gsm_network *msc_network) install_element_ve(&show_bsc_cmd); install_element_ve(&show_msc_conn_cmd); install_element_ve(&show_msc_transaction_cmd); + install_element_ve(&show_nri_cmd); install_element_ve(&sms_send_pend_cmd); install_element_ve(&sms_delete_expired_cmd); @@ -2027,14 +2111,9 @@ void msc_vty_init(struct gsm_network *msc_network) install_element_ve(&subscriber_mstest_open_cmd); install_element_ve(&subscriber_paging_cmd); install_element_ve(&show_stats_cmd); - install_element_ve(&show_smsqueue_cmd); install_element_ve(&logging_fltr_imsi_cmd); install_element(ENABLE_NODE, &ena_subscr_expire_cmd); - install_element(ENABLE_NODE, &smsqueue_trigger_cmd); - install_element(ENABLE_NODE, &smsqueue_max_cmd); - install_element(ENABLE_NODE, &smsqueue_clear_cmd); - install_element(ENABLE_NODE, &smsqueue_fail_cmd); install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd); install_element(ENABLE_NODE, &subscriber_sms_delete_all_cmd); diff --git a/src/libmsc/msub.c b/src/libmsc/msub.c index 2021ed827..ac93665a7 100644 --- a/src/libmsc/msub.c +++ b/src/libmsc/msub.c @@ -1,6 +1,6 @@ /* Manage all MSC roles of a connected subscriber (MSC-A, MSC-I, MSC-T) */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -488,9 +488,9 @@ static void _msub_update_id(struct msub *msub, const char *subscr_name) } /* Compose an ID almost like gsm48_mi_to_string(), but print the MI type along, and print a TMSI as hex. */ -void msub_update_id_from_mi(struct msub *msub, const uint8_t mi[], uint8_t mi_len) +void msub_update_id_from_mi(struct msub *msub, const struct osmo_mobile_identity *mi) { - _msub_update_id(msub, osmo_mi_name(mi, mi_len)); + _msub_update_id(msub, osmo_mobile_identity_to_str_c(OTC_SELECT, mi)); } /* Update msub->fi id string from current msub->vsub and msub->complete_layer3_type. */ @@ -544,6 +544,8 @@ void msc_role_forget_conn(struct osmo_fsm_inst *role, struct ran_conn *conn) *conn_p = NULL; } +/* NOTE: the resulting message buffer will be attached to OTC_SELECT, so its lifetime + * is limited by the current select() loop iteration. Use talloc_steal() to avoid this. */ struct msgb *msc_role_ran_encode(struct osmo_fsm_inst *fi, const struct ran_msg *ran_msg) { struct msc_role_common *c = fi->priv; @@ -556,6 +558,8 @@ struct msgb *msc_role_ran_encode(struct osmo_fsm_inst *fi, const struct ran_msg msg = c->ran->ran_encode(fi, ran_msg); if (!msg) LOGPFSML(fi, LOGL_ERROR, "Failed to encode %s\n", ran_msg_type_name(ran_msg->msg_type)); + else + talloc_steal(OTC_SELECT, msg); return msg; } diff --git a/src/libmsc/neighbor_ident.c b/src/libmsc/neighbor_ident.c index 5120e168e..b3cdf17c4 100644 --- a/src/libmsc/neighbor_ident.c +++ b/src/libmsc/neighbor_ident.c @@ -1,6 +1,6 @@ /* Manage identity of neighboring BSS cells for inter-MSC handover. */ /* - * (C) 2018-2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ diff --git a/src/libmsc/paging.c b/src/libmsc/paging.c index 743ce5c86..9b3dad5d2 100644 --- a/src/libmsc/paging.c +++ b/src/libmsc/paging.c @@ -1,5 +1,5 @@ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -84,7 +84,7 @@ struct paging_request *paging_request_start(struct vlr_subscr *vsub, enum paging { int rc; struct paging_request *pr; - struct gsm_network *net = vsub->vlr->user_ctx; + int paging_response_timer; pr = talloc(vsub, struct paging_request); OSMO_ASSERT(pr); @@ -110,8 +110,9 @@ struct paging_request *paging_request_start(struct vlr_subscr *vsub, enum paging /* reduced on the first paging callback */ vlr_subscr_get(vsub, VSUB_USE_PAGING); vsub->cs.is_paging = true; + paging_response_timer = osmo_tdef_get(msc_ran_infra[vsub->cs.attached_via_ran].tdefs, -4, OSMO_TDEF_S, 10); osmo_timer_setup(&vsub->cs.paging_response_timer, paging_response_timer_cb, vsub); - osmo_timer_schedule(&vsub->cs.paging_response_timer, net->paging_response_timer, 0); + osmo_timer_schedule(&vsub->cs.paging_response_timer, paging_response_timer, 0); } llist_add_tail(&pr->entry, &vsub->cs.requests); @@ -119,6 +120,34 @@ struct paging_request *paging_request_start(struct vlr_subscr *vsub, enum paging return pr; } +/* Two subscribers (e.g. an old TMSI and a new TMSI) turn out to have the same identity, so in order to discard one of + * them, transfer any pending Paging requests to the vsub that will survive. */ +void paging_request_join_vsub(struct vlr_subscr *keep_vsub, struct vlr_subscr *discarding_vsub) +{ + struct paging_request *pr; + + if (!discarding_vsub->cs.is_paging) + return; + + /* transfer all Paging Response callbacks */ + while ((pr = llist_first_entry_or_null(&discarding_vsub->cs.requests, struct paging_request, entry))) { + llist_del(&pr->entry); + talloc_steal(keep_vsub, pr); + llist_add_tail(&pr->entry, &keep_vsub->cs.requests); + } + + /* make sure a Paging use count is present on keep_vsub, if needed */ + if (!keep_vsub->cs.is_paging && !llist_empty(&keep_vsub->cs.requests)) { + vlr_subscr_get(keep_vsub, VSUB_USE_PAGING); + keep_vsub->cs.is_paging = true; + } + + /* Already made sure at the top of this function that discarding_vsub->cs.is_paging == true */ + discarding_vsub->cs.is_paging = false; + osmo_timer_del(&discarding_vsub->cs.paging_response_timer); + vlr_subscr_put(discarding_vsub, VSUB_USE_PAGING); +} + void paging_request_remove(struct paging_request *pr) { struct gsm_trans *trans = pr->trans; @@ -137,6 +166,11 @@ static void paging_concludes(struct vlr_subscr *vsub, struct msc_a *msc_a) struct paging_request *pr, *pr_next; struct paging_signal_data sig_data; + if (!vsub) { + /* A Paging Response has no subscriber. (Related: OS#4449) */ + return; + } + osmo_timer_del(&vsub->cs.paging_response_timer); llist_for_each_entry_safe(pr, pr_next, &vsub->cs.requests, entry) { diff --git a/src/libmsc/ran_conn.c b/src/libmsc/ran_conn.c index 8418c9eb5..07638016a 100644 --- a/src/libmsc/ran_conn.c +++ b/src/libmsc/ran_conn.c @@ -1,7 +1,7 @@ /* MSC RAN connection implementation */ /* - * (C) 2016-2018 by sysmocom s.m.f.c. <info@sysmocom.de> + * (C) 2016-2018 by sysmocom s.f.m.c. <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr diff --git a/src/libmsc/ran_infra.c b/src/libmsc/ran_infra.c index af4054149..6a178403f 100644 --- a/src/libmsc/ran_infra.c +++ b/src/libmsc/ran_infra.c @@ -1,6 +1,6 @@ /* Lookup table for various RAN implementations */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -43,6 +43,7 @@ const struct value_string an_proto_names[] = { { .T = -1, .default_val = 5, .desc = "RAN connection Complete Layer 3, Authentication and Ciphering timeout" }, \ { .T = -2, .default_val = 30, .desc = "RAN connection release sanity timeout" }, \ { .T = -3, .default_val = 10, .desc = "Timeout to find a target BSS after Handover Required" }, \ + { .T = -4, .default_val = 10, .desc = "Paging response timeout" }, \ struct osmo_tdef msc_tdefs_geran[] = { RAN_TDEFS @@ -55,6 +56,7 @@ struct osmo_tdef msc_tdefs_utran[] = { }; struct osmo_tdef msc_tdefs_sgs[] = { + { .T = -4, .default_val = 10, .desc = "Paging response timeout" }, {} }; @@ -106,6 +108,16 @@ struct ran_infra msc_ran_infra[] = { .ran_dec_l2 = ran_iu_decode_l2, .ran_encode = ran_iu_encode, #endif + .force_mgw_codecs_to_ran = { + .count = 1, + .codec = { + { + .payload_type = 96, + .subtype_name = "VND.3GPP.IUFP", + .rate = 16000, + }, + }, + }, }, [OSMO_RAT_EUTRAN_SGS] = { .type = OSMO_RAT_EUTRAN_SGS, diff --git a/src/libmsc/ran_msg.c b/src/libmsc/ran_msg.c index 46816a961..3e4b20c50 100644 --- a/src/libmsc/ran_msg.c +++ b/src/libmsc/ran_msg.c @@ -1,25 +1,21 @@ /* Common bits for RAN message handling */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <osmocom/core/utils.h> @@ -55,6 +51,25 @@ const struct value_string ran_msg_type_names[] = { { RAN_MSG_HANDOVER_DETECT, "HANDOVER_DETECT" }, { RAN_MSG_HANDOVER_COMPLETE, "HANDOVER_COMPLETE" }, { RAN_MSG_HANDOVER_FAILURE, "HANDOVER_FAILURE" }, + { RAN_MSG_VGCS_VBS_SETUP, "VGCS_VBS_SETUP" }, + { RAN_MSG_VGCS_VBS_SETUP_ACK, "VGCS_VBS_SETUP_ACK" }, + { RAN_MSG_VGCS_VBS_SETUP_REFUSE, "VGCS_VBS_SETUP_REFUSE" }, + { RAN_MSG_VGCS_VBS_ASSIGN_REQ, "VGCS_VBS_ASSIGN_REQ" }, + { RAN_MSG_VGCS_VBS_ASSIGN_RES, "VGCS_VBS_ASSIGN_RES" }, + { RAN_MSG_VGCS_VBS_ASSIGN_FAIL, "VGCS_VBS_ASSIGN_FAIL" }, + { RAN_MSG_VGCS_VBS_QUEUING_IND, "VGCS_VBS_QUEUING_IND" }, + { RAN_MSG_UPLINK_REQUEST, "UPLINK_REQUEST" }, + { RAN_MSG_UPLINK_REQUEST_ACK, "UPLINK_REQUEST_ACK" }, + { RAN_MSG_UPLINK_REQUEST_CNF, "UPLINK_REQUEST_CNF" }, + { RAN_MSG_UPLINK_APPLICATION_DATA, "UPLINK_APPLICATION_DATA" }, + { RAN_MSG_UPLINK_RELEASE_IND, "UPLINK_RELEASE_IND" }, + { RAN_MSG_UPLINK_REJECT_CMD, "UPLINK_REJECT_CMD" }, + { RAN_MSG_UPLINK_RELEASE_CMD, "UPLINK_RELEASE_CMD" }, + { RAN_MSG_UPLINK_SEIZED_CMD, "UPLINK_SEIZED_CMD" }, + { RAN_MSG_VGCS_ADDITIONAL_INFO, "VGCS_ADDITIONAL_INFO" }, + { RAN_MSG_VGCS_VBS_AREA_CELL_INFO, "VGCS_VBS_AREA_CELL_INFO" }, + { RAN_MSG_VGCS_VBS_ASSIGN_STATUS, "VGCS_VBS_ASSIGN_STATUS" }, + { RAN_MSG_VGCS_SMS, "VGCS_SMS" }, {} }; diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c index 7672d863d..d9041204a 100644 --- a/src/libmsc/ran_msg_a.c +++ b/src/libmsc/ran_msg_a.c @@ -1,30 +1,27 @@ /* BSSAP/BSSMAP encoding and decoding for MSC */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <osmocom/core/byteswap.h> #include <osmocom/crypt/auth.h> +#include <osmocom/crypt/kdf.h> #include <osmocom/gsm/tlv.h> #include <osmocom/gsm/gsm0808.h> @@ -52,9 +49,11 @@ static int ran_a_decode_l3_compl(struct ran_dec *ran_dec, struct msgb *msg, stru struct gsm0808_cell_id cell_id; struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER); struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION); + struct tlv_p_entry *ie_codec_list_bss_supported = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST); + struct gsm0808_speech_codec_list codec_list_bss_supported; struct ran_msg ran_dec_msg = { .msg_type = RAN_MSG_COMPL_L3, - .msg_name = "BSSMAP Complete Layer 3", + .msg_name = "BSSMAP Complete Layer 3 Information", .compl_l3 = { .cell_id = &cell_id, .msg = msg, @@ -105,7 +104,7 @@ static int ran_a_decode_l3_compl(struct ran_dec *ran_dec, struct msgb *msg, stru .id = cil.id_list[0], }; - /* Parse Layer 3 Information element */ + /* Parse Layer 3 Information element; point ran_dec_msg->compl_l3.msg to the L3 Info data */ msg->l3h = (uint8_t*)ie_l3_info->val; msgb_l3trim(msg, ie_l3_info->len); @@ -114,6 +113,19 @@ static int ran_a_decode_l3_compl(struct ran_dec *ran_dec, struct msgb *msg, stru return -ENODATA; } + /* Decode Codec List (BSS Supported) */ + if (ie_codec_list_bss_supported) { + rc = gsm0808_dec_speech_codec_list(&codec_list_bss_supported, + ie_codec_list_bss_supported->val, ie_codec_list_bss_supported->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, + "Complete Layer 3 Information: unable to decode IE Codec List (BSS Supported)" + " (rc=%d), continuing anyway\n", rc); + /* This IE is not critical, do not abort with error. */ + } else + ran_dec_msg.compl_l3.codec_list_bss_supported = &codec_list_bss_supported; + } + return ran_decoded(ran_dec, &ran_dec_msg); } @@ -210,7 +222,7 @@ static int ran_a_decode_cipher_mode_reject(struct ran_dec *ran_dec, struct msgb .msg_name = "BSSMAP Ciphering Mode Reject", }; - rc = gsm0808_get_cipher_reject_cause(tp); + rc = gsm0808_get_cause(tp); if (rc < 0) { LOG_RAN_A_DEC_MSG(LOGL_ERROR, "failed to extract Cause\n"); ran_dec_msg.cipher_mode_reject.bssap_cause = GSM0808_CAUSE_EQUIPMENT_FAILURE; @@ -226,34 +238,26 @@ enum mgcp_codecs ran_a_mgcp_codec_from_sc(const struct gsm0808_speech_codec *sc) switch (sc->type) { case GSM0808_SCT_FR1: return CODEC_GSM_8000_1; - break; case GSM0808_SCT_FR2: return CODEC_GSMEFR_8000_1; - break; case GSM0808_SCT_FR3: return CODEC_AMR_8000_1; - break; case GSM0808_SCT_FR4: return CODEC_AMRWB_16000_1; - break; case GSM0808_SCT_FR5: return CODEC_AMRWB_16000_1; - break; case GSM0808_SCT_HR1: return CODEC_GSMHR_8000_1; - break; case GSM0808_SCT_HR3: return CODEC_AMR_8000_1; - break; case GSM0808_SCT_HR4: return CODEC_AMRWB_16000_1; - break; case GSM0808_SCT_HR6: return CODEC_AMRWB_16000_1; - break; + case GSM0808_SCT_CSD: + return CODEC_CLEARMODE; default: return CODEC_PCMU_8000_1; - break; } } @@ -261,10 +265,10 @@ static int ran_a_decode_assignment_complete(struct ran_dec *ran_dec, struct msgb { struct tlv_p_entry *ie_aoip_transp_addr = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR); struct tlv_p_entry *ie_speech_codec = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC); + struct tlv_p_entry *ie_codec_list_bss_supported = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST); struct tlv_p_entry *ie_osmux_cid = TLVP_GET(tp, GSM0808_IE_OSMO_OSMUX_CID); struct sockaddr_storage rtp_addr; - struct sockaddr_in *rtp_addr_in; - struct gsm0808_speech_codec sc; + struct gsm0808_speech_codec_list codec_list_bss_supported; int rc; struct ran_msg ran_dec_msg = { .msg_type = RAN_MSG_ASSIGNMENT_COMPLETE, @@ -279,15 +283,7 @@ static int ran_a_decode_assignment_complete(struct ran_dec *ran_dec, struct msgb return -EINVAL; } - rtp_addr_in = (struct sockaddr_in*)&rtp_addr; - - if (rtp_addr.ss_family != AF_INET) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Assignment Complete: IE AoIP Transport Address:" - " unsupported addressing scheme (only IPV4 supported)\n"); - return -EINVAL; - } - - if (osmo_sockaddr_str_from_sockaddr_in(&ran_dec_msg.assignment_complete.remote_rtp, rtp_addr_in)) { + if (osmo_sockaddr_str_from_sockaddr(&ran_dec_msg.assignment_complete.remote_rtp, &rtp_addr)) { LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Assignment Complete: unable to decode remote RTP IP address\n"); return -EINVAL; } @@ -304,14 +300,27 @@ static int ran_a_decode_assignment_complete(struct ran_dec *ran_dec, struct msgb if (ie_speech_codec) { /* Decode Speech Codec (Chosen) element */ - rc = gsm0808_dec_speech_codec(&sc, ie_speech_codec->val, ie_speech_codec->len); + rc = gsm0808_dec_speech_codec(&ran_dec_msg.assignment_complete.codec, + ie_speech_codec->val, ie_speech_codec->len); if (rc < 0) { LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Assignment Complete: unable to decode IE Speech Codec (Chosen)" " (rc=%d).\n", rc); return -EINVAL; } ran_dec_msg.assignment_complete.codec_present = true; - ran_dec_msg.assignment_complete.codec = ran_a_mgcp_codec_from_sc(&sc); + } + + if (ie_codec_list_bss_supported) { + /* Decode Codec List (BSS Supported) */ + rc = gsm0808_dec_speech_codec_list(&codec_list_bss_supported, + ie_codec_list_bss_supported->val, ie_codec_list_bss_supported->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, + "Assignment Complete: unable to decode IE Codec List (BSS Supported)" + " (rc=%d), continuing anyway\n", rc); + /* This IE is not critical, do not abort with error. */ + } else + ran_dec_msg.assignment_complete.codec_list_bss_supported = &codec_list_bss_supported; } return ran_decoded(ran_dec, &ran_dec_msg); @@ -461,13 +470,13 @@ static int ran_a_decode_handover_request(struct ran_dec *ran_dec, const struct m const struct tlv_p_entry *ie_aoip_transp_addr = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR); const struct tlv_p_entry *ie_codec_list_msc_preferred = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST); const struct tlv_p_entry *ie_call_id = TLVP_GET(tp, GSM0808_IE_CALL_ID); + const struct tlv_p_entry *ie_kc128 = TLVP_GET(tp, GSM0808_IE_KC_128); const struct tlv_p_entry *ie_global_call_ref = TLVP_GET(tp, GSM0808_IE_GLOBAL_CALL_REF); struct gsm0808_channel_type channel_type; struct gsm0808_encrypt_info encr_info; struct gsm0808_speech_codec_list scl; struct geran_encr geran_encr = {}; - char imsi[OSMO_IMSI_BUF_SIZE]; struct osmo_sockaddr_str rtp_ran_local; if (!ie_channel_type) { @@ -484,7 +493,7 @@ static int ran_a_decode_handover_request(struct ran_dec *ran_dec, const struct m int i; if (gsm0808_dec_encrypt_info(&encr_info, ie_encryption_information->val, ie_encryption_information->len) <= 0) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Encryption Informaiton IE\n"); + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Encryption Information IE\n"); return -EINVAL; } @@ -494,7 +503,7 @@ static int ran_a_decode_handover_request(struct ran_dec *ran_dec, const struct m } if (encr_info.key_len > sizeof(geran_encr.key)) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Encryption Informaiton IE:" + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Encryption Information IE:" " encryption key is too long: %u\n", geran_encr.key_len); return -EINVAL; } @@ -504,6 +513,11 @@ static int ran_a_decode_handover_request(struct ran_dec *ran_dec, const struct m geran_encr.key_len = encr_info.key_len; } + if (ie_kc128) { + memcpy(geran_encr.kc128, ie_kc128->val, 16); + geran_encr.kc128_present = true; + } + r->geran.chosen_encryption = &geran_encr; } @@ -578,28 +592,22 @@ static int ran_a_decode_handover_request(struct ran_dec *ran_dec, const struct m } if (ie_imsi) { - gsm48_mi_to_string(imsi, sizeof(imsi), ie_imsi->val, ie_imsi->len); - r->imsi = imsi; + struct osmo_mobile_identity mi; + if (osmo_mobile_identity_decode(&mi, ie_imsi->val, ie_imsi->len, false) + || mi.type != GSM_MI_TYPE_IMSI) + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "IE IMSI: cannot decode IMSI identity\n"); + else + r->imsi = mi.imsi; } if (ie_aoip_transp_addr) { - do { - struct sockaddr_storage rtp_addr; - if (gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len) < 0) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n"); - break; - } - if (rtp_addr.ss_family != AF_INET) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "IE AoIP Transport Address:" - " unsupported addressing scheme (only IPV4 supported)\n"); - break; - } - if (osmo_sockaddr_str_from_sockaddr_in(&rtp_ran_local, (struct sockaddr_in*)&rtp_addr)) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode remote RTP IP address\n"); - break; - } + struct sockaddr_storage rtp_addr; + if (gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len) < 0) + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n"); + else if (osmo_sockaddr_str_from_sockaddr(&rtp_ran_local, &rtp_addr) < 0) + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode remote RTP IP address\n"); + else r->rtp_ran_local = &rtp_ran_local; - } while(0); } if (ie_codec_list_msc_preferred @@ -655,45 +663,32 @@ static int ran_a_decode_handover_request_ack(struct ran_dec *ran_dec, const stru } if (ie_chosen_speech_version) { - struct gsm0808_speech_codec sc; ran_dec_msg.handover_request_ack.chosen_speech_version = ie_chosen_speech_version->val[0]; /* the codec may be extrapolated from this Speech Version or below from Speech Codec */ - gsm0808_speech_codec_from_chan_type(&sc, ran_dec_msg.handover_request_ack.chosen_speech_version); - ran_dec_msg.handover_request_ack.codec_present = true; - ran_dec_msg.handover_request_ack.codec = ran_a_mgcp_codec_from_sc(&sc); + if (gsm0808_speech_codec_from_chan_type(&ran_dec_msg.handover_request_ack.codec, + ran_dec_msg.handover_request_ack.chosen_speech_version) == 0) + ran_dec_msg.handover_request_ack.codec_present = true; } if (ie_aoip_transp_addr) { - do { - struct sockaddr_storage rtp_addr; - if (gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len) < 0) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n"); - break; - } - if (rtp_addr.ss_family != AF_INET) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "IE AoIP Transport Address:" - " unsupported addressing scheme (only IPV4 supported)\n"); - break; - } - if (osmo_sockaddr_str_from_sockaddr_in(&ran_dec_msg.handover_request_ack.remote_rtp, - (struct sockaddr_in*)&rtp_addr)) { - LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode remote RTP IP address\n"); - ran_dec_msg.handover_request_ack.remote_rtp = (struct osmo_sockaddr_str){}; - break; - } - } while(0); + struct sockaddr_storage rtp_addr; + if (gsm0808_dec_aoip_trasp_addr(&rtp_addr, ie_aoip_transp_addr->val, ie_aoip_transp_addr->len) < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n"); + } else if (osmo_sockaddr_str_from_sockaddr(&ran_dec_msg.handover_request_ack.remote_rtp, + &rtp_addr)) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode remote RTP IP address\n"); + ran_dec_msg.handover_request_ack.remote_rtp = (struct osmo_sockaddr_str){}; + } } if (ie_speech_codec) { - struct gsm0808_speech_codec sc; - if (gsm0808_dec_speech_codec(&sc, ie_speech_codec->val, ie_speech_codec->len) < 0) + /* the codec may be extrapolated from above Speech Version or from this Speech Codec */ + if (gsm0808_dec_speech_codec(&ran_dec_msg.handover_request_ack.codec, + ie_speech_codec->val, ie_speech_codec->len) < 0) LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode IE Speech Codec (Chosen)\n"); - else { - /* the codec may be extrapolated from above Speech Version or from this Speech Codec */ + else ran_dec_msg.handover_request_ack.codec_present = true; - ran_dec_msg.handover_request_ack.codec = ran_a_mgcp_codec_from_sc(&sc); - } } return ran_decoded(ran_dec, &ran_dec_msg); @@ -739,6 +734,439 @@ static int ran_a_decode_handover_failure(struct ran_dec *ran_dec, const struct m return ran_decoded(ran_dec, &ran_dec_msg); } +static int ran_a_decode_vgcs_vbs_setup_ack(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_VGCS_VBS_SETUP_ACK, + .msg_name = "BSSMAP VGCS/VBS SETUP ACKNOWLEDGE", + }; + struct gsm0808_vgcs_vbs_setup_ack *r = &ran_dec_msg.vgcs_vbs_setup_ack; + int rc; + + const struct tlv_p_entry *ie_flags = TLVP_GET(tp, GSM0808_IE_VGCS_FEATURE_FLAGS); + + /* VGCS Feature Flags, 3.2.2.88 */ + if (ie_flags) { + rc = gsm0808_dec_vgcs_feature_flags(&r->flags, ie_flags->val, ie_flags->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Unable to decode VGCS/VBS Feature Flags\n"); + return -EINVAL; + } + r->vgcs_feature_flags_present = true; + } + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_vgcs_vbs_setup_refuse(struct ran_dec *ran_dec, const struct msgb *msg, + const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_VGCS_VBS_SETUP_REFUSE, + .msg_name = "BSSMAP VGCS/VBS SETUP REFUSE", + }; + + const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE); + + /* Cause, 3.2.2.5 */ + if (!ie_cause || ie_cause->len < 1) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cause\n"); + return -EINVAL; + } + ran_dec_msg.vgcs_vbs_setup_refuse.cause = ie_cause->val[0]; + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_vgcs_vbs_assign_res(struct ran_dec *ran_dec, const struct msgb *msg, + const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_RES, + .msg_name = "BSSMAP VGCS/VBS ASSIGNMENT RESULT", + }; + struct gsm0808_vgcs_vbs_assign_res *r = &ran_dec_msg.vgcs_vbs_assign_res; + int rc; + + const struct tlv_p_entry *ie_channel_type = TLVP_GET(tp, GSM0808_IE_CHANNEL_TYPE); + const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER); + const struct tlv_p_entry *ie_chosen_channel = TLVP_GET(tp, GSM0808_IE_CHOSEN_CHANNEL); + const struct tlv_p_entry *ie_cic = TLVP_GET(tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE); + const struct tlv_p_entry *ie_circuit_pool = TLVP_GET(tp, GSM0808_IE_CIRCUIT_POOL); + const struct tlv_p_entry *ie_aoip = TLVP_GET(tp, GSM0808_IE_AOIP_TRASP_ADDR); + const struct tlv_p_entry *ie_call_id = TLVP_GET(tp, GSM0808_IE_CALL_ID); + + /* Channel Type, 3.2.2.11 */ + if (!ie_channel_type) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Channel Type\n"); + return -EINVAL; + } + if (gsm0808_dec_channel_type(&r->channel_type, ie_channel_type->val, ie_channel_type->len) <= 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Failed to decode Channel Type IE\n"); + return -EINVAL; + } + + /* Cell Identifier, 3.2.2.17 */ + if (!ie_cell_id) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier\n"); + return -EINVAL; + } + rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + + /* Chosen Channel, 3.2.2.33 */ + if (ie_chosen_channel) { + r->chosen_channel = ie_chosen_channel->val[0]; + r->chosen_channel_present = true; + } + + /* Circuit Identity Code, 3.2.2.2 */ + if (ie_cic) { + if (ie_cic->len != 2) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Circuit Identity Code has invalid length.\n"); + return -EINVAL; + } + r->cic = *(uint16_t *)ie_cic->val; + r->cic_present = true; + } + + /* Circuit Pool, 3.2.2.45 */ + if (ie_circuit_pool) { + r->circuit_pool = ie_circuit_pool->val[0]; + r->circuit_pool_present = true; + } + + /* AoIP Transport Layer Address (BSS), 3.2.2.102 */ + if (ie_aoip) { + if (gsm0808_dec_aoip_trasp_addr(&r->aoip_transport_layer, ie_aoip->val, ie_aoip->len) < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "unable to decode AoIP transport address\n"); + return -EINVAL; + } + r->aoip_transport_layer_present = true; + } + + if (ie_call_id) { + if (ie_call_id->len != 4) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n"); + return -EINVAL; + } + r->call_id = osmo_load32le(ie_call_id->val); + r->call_id_present = true; + } + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_vgcs_vbs_assign_fail(struct ran_dec *ran_dec, const struct msgb *msg, + const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_FAIL, + .msg_name = "BSSMAP VGCS/VBS ASSIGNMENT FAILURE", + }; + struct gsm0808_vgcs_vbs_assign_fail *r = &ran_dec_msg.vgcs_vbs_assign_fail; + int rc; + + const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE); + const struct tlv_p_entry *ie_circuit_pool = TLVP_GET(tp, GSM0808_IE_CIRCUIT_POOL); + const struct tlv_p_entry *ie_circuit_pool_list = TLVP_GET(tp, GSM0808_IE_CIRCUIT_POOL_LIST); + const struct tlv_p_entry *ie_codec_list_bss_supported = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST); + + /* Cause, 3.2.2.5 */ + if (!ie_cause || ie_cause->len < 1) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cause\n"); + return -EINVAL; + } + r->cause = ie_cause->val[0]; + + /* Circuit Pool, 3.2.2.45 */ + if (ie_circuit_pool) { + r->circuit_pool = ie_circuit_pool->val[0]; + r->circuit_pool_present = true; + } + + /* Circuit Pool List, 3.2.2.46 */ + if (ie_circuit_pool_list && ie_circuit_pool_list->len) { + if (ie_circuit_pool_list->len > CIRCUIT_POOL_LIST_MAXLEN) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Circuit Pool List has invalid length.\n"); + return -EINVAL; + } + memcpy(r->cpl.pool, ie_circuit_pool_list->val, ie_circuit_pool_list->len); + r->cpl.list_len = ie_circuit_pool_list->len; + r->cpl_present = true; + } + + /* Codec List (BSS Supported) 3.2.2.103 */ + if (ie_codec_list_bss_supported) { + rc = gsm0808_dec_speech_codec_list(&r->codec_list_bss_supported, + ie_codec_list_bss_supported->val, ie_codec_list_bss_supported->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, + "Complete Layer 3 Information: unable to decode IE Codec List (BSS Supported)" + " (rc=%d), continuing anyway\n", rc); + /* This IE is not critical, do not abort with error. */ + } else + r->codec_list_present = true; + } + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_vgcs_vbs_queuing_ind(struct ran_dec *ran_dec, const struct msgb *msg, + const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_VGCS_VBS_QUEUING_IND, + .msg_name = "BSSMAP VGCS/VBS QUEUING INDICATION", + }; + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_uplink_request(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_UPLINK_REQUEST, + .msg_name = "BSSMAP UPLINK REQUEST", + }; + struct gsm0808_uplink_request *r = &ran_dec_msg.uplink_request; + int rc; + + const struct tlv_p_entry *ie_talker_priority = TLVP_GET(tp, GSM0808_IE_TALKER_PRIORITY); + const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER); + const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION); + const struct tlv_p_entry *ie_mi = TLVP_GET(tp, GSM0808_IE_MOBILE_IDENTITY); + + /* Talker Priority, 3.2.2.89 */ + if (ie_talker_priority) { + r->talker_priority = ie_talker_priority->val[0] & 0x03; + r->talker_priority_present = true; + } + + /* Cell Identifier, 3.2.2.17 */ + if (ie_cell_id) { + rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + } + + /* Layer 3 Information, 3.2.2.24 */ + if (ie_l3_info && ie_l3_info->len) { + if (ie_l3_info->len > LAYER_3_INFORMATION_MAXLEN) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n"); + return -EINVAL; + } + memcpy(r->l3.l3, ie_l3_info->val, ie_l3_info->len); + r->l3.l3_len = ie_l3_info->len; + r->l3_present = true; + } + + /* Mobile Identity, 3.2.2.41 */ + if (ie_mi) { + rc = osmo_mobile_identity_decode(&r->mi, ie_mi->val, ie_mi->len, false); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Mobile Identity gave rc=%d\n", rc); + return -EINVAL; + } + r->mi_present = true; + } + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_uplink_request_cnf(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_UPLINK_REQUEST_CNF, + .msg_name = "BSSMAP UPLINK REQUEST CONFIRM", + }; + struct gsm0808_uplink_request_cnf *r = &ran_dec_msg.uplink_request_cnf; + int rc; + + const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER); + const struct tlv_p_entry *ie_talker_identity = TLVP_GET(tp, GSM0808_IE_TALKER_IDENTITY); + const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION); + + /* Cell Identifier, 3.2.2.17 */ + if (!ie_cell_id) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier\n"); + return -EINVAL; + } + rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + + /* Talker Identity, 3.2.2.91 */ + if (ie_talker_identity) { + rc = gsm0808_dec_talker_identity(&r->talker_identity, ie_talker_identity->val, ie_talker_identity->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Talker Identity gave rc=%d\n", rc); + return -EINVAL; + } + r->talker_identity_present = true; + } + + /* Layer 3 Information, 3.2.2.24 */ + if (!ie_l3_info) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Layer 3 Information\n"); + return -EINVAL; + } + if (ie_l3_info->len > LAYER_3_INFORMATION_MAXLEN) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n"); + return -EINVAL; + } + memcpy(r->l3.l3, ie_l3_info->val, ie_l3_info->len); + r->l3.l3_len = ie_l3_info->len; + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_uplink_application_data(struct ran_dec *ran_dec, const struct msgb *msg, + const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_UPLINK_APPLICATION_DATA, + .msg_name = "BSSMAP UPLINK APPLICATION DATA", + }; + struct gsm0808_uplink_app_data *r = &ran_dec_msg.uplink_app_data; + int rc; + + const struct tlv_p_entry *ie_cell_id = TLVP_GET(tp, GSM0808_IE_CELL_IDENTIFIER); + const struct tlv_p_entry *ie_l3_info = TLVP_GET(tp, GSM0808_IE_LAYER_3_INFORMATION); + const struct tlv_p_entry *ie_app_data = TLVP_GET(tp, GSM0808_IE_APP_DATA); + + /* Cell Identifier, 3.2.2.17 */ + if (!ie_cell_id) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cell Identifier\n"); + return -EINVAL; + } + rc = gsm0808_dec_cell_id(&r->cell_identifier, ie_cell_id->val, ie_cell_id->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + + /* Layer 3 Information, 3.2.2.24 */ + if (!ie_l3_info) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Layer 3 Information\n"); + return -EINVAL; + } + if (ie_l3_info->len > LAYER_3_INFORMATION_MAXLEN) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Call Identifier has invalid length.\n"); + return -EINVAL; + } + memcpy(r->l3.l3, ie_l3_info->val, ie_l3_info->len); + r->l3.l3_len = ie_l3_info->len; + + /* Application Data Information, 3.2.2.100 */ + if (!ie_app_data || ie_app_data->len < 1) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Application Data Information\n"); + return -EINVAL; + } + r->bt_ind = ie_app_data->val[0] & 0x01; + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_uplink_release_ind(struct ran_dec *ran_dec, const struct msgb *msg, const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_UPLINK_RELEASE_IND, + .msg_name = "BSSMAP UPLINK RELEASE INDICATION", + }; + struct gsm0808_uplink_release_ind *r = &ran_dec_msg.uplink_release_ind; + + const struct tlv_p_entry *ie_cause = TLVP_GET(tp, GSM0808_IE_CAUSE); + const struct tlv_p_entry *ie_talker_priority = TLVP_GET(tp, GSM0808_IE_TALKER_PRIORITY); + + /* Cause, 3.2.2.5 */ + if (!ie_cause || ie_cause->len < 1) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Mandatory IE missing: Cause\n"); + return -EINVAL; + } + r->cause = ie_cause->val[0]; + + /* Talker Priority, 3.2.2.89 */ + if (ie_talker_priority) { + r->talker_priority = ie_talker_priority->val[0] & 0x03; + r->talker_priority_present = true; + } + + return ran_decoded(ran_dec, &ran_dec_msg); +} + +static int ran_a_decode_vgcs_vbs_assign_status(struct ran_dec *ran_dec, const struct msgb *msg, + const struct tlv_parsed *tp) +{ + struct ran_msg ran_dec_msg = { + .msg_type = RAN_MSG_VGCS_VBS_ASSIGN_STATUS, + .msg_name = "BSSMAP VGCS/VBS ASSIGNMENT STATUS", + }; + struct gsm0808_vgcs_vbs_assign_stat *r = &ran_dec_msg.vgcs_vbs_assign_stat; + int rc; + + const struct tlv_p_entry *ie_cils_est = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_EST_CELLS); + const struct tlv_p_entry *ie_cils_tbe = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_CELLS_TBE); + const struct tlv_p_entry *ie_cils_rel = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_REL_CELLS); + const struct tlv_p_entry *ie_cils_ne = TLVP_GET(tp, GSM0808_IE_CELL_ID_LIST_SEG_NE_CELLS); + const struct tlv_p_entry *ie_cell_status = TLVP_GET(tp, GSM0808_IE_VGCS_VBS_CELL_STATUS); + + /* Cell Identifier List Segment, 3.2.2.27b */ + if (ie_cils_est) { + rc = gsm0808_dec_cell_id_list_segment(&r->cils_est, ie_cils_est->val, ie_cils_est->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + r->cils_est_present = true; + } + + /* Cell Identifier List Segment, 3.2.2.27c */ + if (ie_cils_tbe) { + rc = gsm0808_dec_cell_id_list_segment(&r->cils_tbe, ie_cils_tbe->val, ie_cils_tbe->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + r->cils_tbe_present = true; + } + + /* Cell Identifier List Segment, 3.2.2.27e */ + if (ie_cils_rel) { + rc = gsm0808_dec_cell_id_list_segment(&r->cils_rel, ie_cils_rel->val, ie_cils_rel->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + r->cils_rel_present = true; + } + + /* Cell Identifier List Segment, 3.2.2.27f */ + if (ie_cils_ne) { + rc = gsm0808_dec_cell_id_list_segment(&r->cils_ne, ie_cils_ne->val, ie_cils_ne->len); + if (rc < 0) { + LOG_RAN_A_DEC_MSG(LOGL_ERROR, "Decoding Cell Identifier gave rc=%d\n", rc); + return -EINVAL; + } + r->cils_ne_present = true; + } + + /* VGCS/VBS Cell Status, 3.2.2.94 */ + if (ie_cell_status && ie_cell_status->len) { + r->cell_status = ie_cell_status->val[0] & 0x73; + r->cell_status_present = true; + } + + return ran_decoded(ran_dec, &ran_dec_msg); +} + static int ran_a_decode_bssmap(struct ran_dec *ran_dec, struct msgb *bssmap) { struct tlv_parsed tp[2]; @@ -776,7 +1204,7 @@ static int ran_a_decode_bssmap(struct ran_dec *ran_dec, struct msgb *bssmap) return -EINVAL; } - LOG_RAN_A_DEC(ran_dec, LOGL_DEBUG, "Rx BSSMAP DT1 %s\n", gsm0808_bssmap_name(msg_type)); + LOG_RAN_A_DEC(ran_dec, LOGL_DEBUG, "%s\n", gsm0808_bssmap_name(msg_type)); switch (msg_type) { case BSS_MAP_MSG_COMPLETE_LAYER_3: @@ -810,6 +1238,26 @@ static int ran_a_decode_bssmap(struct ran_dec *ran_dec, struct msgb *bssmap) return ran_a_decode_sapi_n_reject(ran_dec, bssmap, tp); case BSS_MAP_MSG_LCLS_NOTIFICATION: return ran_a_decode_lcls_notification(ran_dec, bssmap, tp); + case BSS_MAP_MSG_VGCS_VBS_SETUP_ACK: + return ran_a_decode_vgcs_vbs_setup_ack(ran_dec, bssmap, tp); + case BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE: + return ran_a_decode_vgcs_vbs_setup_refuse(ran_dec, bssmap, tp); + case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT: + return ran_a_decode_vgcs_vbs_assign_res(ran_dec, bssmap, tp); + case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE: + return ran_a_decode_vgcs_vbs_assign_fail(ran_dec, bssmap, tp); + case BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION: + return ran_a_decode_vgcs_vbs_queuing_ind(ran_dec, bssmap, tp); + case BSS_MAP_MSG_UPLINK_RQST: + return ran_a_decode_uplink_request(ran_dec, bssmap, tp); + case BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION: + return ran_a_decode_uplink_request_cnf(ran_dec, bssmap, tp); + case BSS_MAP_MSG_UPLINK_APP_DATA: + return ran_a_decode_uplink_application_data(ran_dec, bssmap, tp); + case BSS_MAP_MSG_UPLINK_RELEASE_INDICATION: + return ran_a_decode_uplink_release_ind(ran_dec, bssmap, tp); + case BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS: + return ran_a_decode_vgcs_vbs_assign_status(ran_dec, bssmap, tp); /* From current RAN peer, the Handover origin: */ case BSS_MAP_MSG_HANDOVER_REQUIRED: @@ -898,23 +1346,33 @@ static int ran_a_channel_type_to_speech_codec_list(struct gsm0808_speech_codec_l int rc; memset(scl, 0, sizeof(*scl)); - for (i = 0; i < ct->perm_spch_len; i++) { - rc = gsm0808_speech_codec_from_chan_type(&scl->codec[i], ct->perm_spch[i]); - if (rc != 0) - return -EINVAL; + + switch (ct->ch_indctr) { + case GSM0808_CHAN_DATA: + scl->codec[0] = (struct gsm0808_speech_codec) { + .pi = true, /* PI indicates CSDoIP is supported */ + .pt = false, /* PT indicates CSDoTDM is not supported */ + .type = GSM0808_SCT_CSD, + .cfg = 0, /* R2/R3 not set (redundancy not supported) */ + }; + scl->len = 1; + break; + case GSM0808_CHAN_SPEECH: + for (i = 0; i < ct->perm_spch_len; i++) { + rc = gsm0808_speech_codec_from_chan_type(&scl->codec[i], ct->perm_spch[i]); + if (rc != 0) + return -EINVAL; + } + scl->len = i; + break; + default: + OSMO_ASSERT(0); + break; } - scl->len = i; return 0; } -static void _gsm0808_assignment_extend_osmux(struct msgb *msg, uint8_t cid) -{ - OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /*TL not in len */ - msgb_tv_put(msg, GSM0808_IE_OSMO_OSMUX_CID, cid); - msg->l3h[1] = msgb_l3len(msg) - 2; -} - /* Compose a BSSAP Assignment Command. * Passing an RTP address is optional. * The msub is passed merely for error logging. */ @@ -926,6 +1384,7 @@ static struct msgb *ran_a_make_assignment_command(struct osmo_fsm_inst *log_fi, struct sockaddr_storage rtp_addr; struct sockaddr_storage *use_rtp_addr = NULL; struct msgb *msg; + const uint32_t *call_id = NULL; int rc; if (!ac->channel_type) { @@ -933,7 +1392,7 @@ static struct msgb *ran_a_make_assignment_command(struct osmo_fsm_inst *log_fi, return NULL; } - if (ac->channel_type->ch_indctr == GSM0808_CHAN_SPEECH) { + if (ac->channel_type->ch_indctr == GSM0808_CHAN_SPEECH || ac->channel_type->ch_indctr == GSM0808_CHAN_DATA) { rc = ran_a_channel_type_to_speech_codec_list(&scl, ac->channel_type); if (rc < 0) { LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: Cannot translate Channel Type to Speech Codec List\n"); @@ -942,33 +1401,71 @@ static struct msgb *ran_a_make_assignment_command(struct osmo_fsm_inst *log_fi, use_scl = &scl; /* Package RTP-Address data */ - if (osmo_sockaddr_str_is_set(ac->cn_rtp)) { - struct sockaddr_in rtp_addr_in; - - memset(&rtp_addr_in, 0, sizeof(rtp_addr_in)); - rtp_addr_in.sin_family = AF_INET; - rtp_addr_in.sin_port = osmo_htons(ac->cn_rtp->port), - rtp_addr_in.sin_addr.s_addr = inet_addr(ac->cn_rtp->ip); - - if (rtp_addr_in.sin_addr.s_addr == INADDR_NONE) { - LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: Invalid RTP-Address\n"); - return NULL; - } - if (rtp_addr_in.sin_port == 0) { - LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Assignment Command: Invalid RTP-Port\n"); + if (osmo_sockaddr_str_is_nonzero(ac->cn_rtp)) { + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + int family = osmo_ip_str_type(ac->cn_rtp->ip); + switch (family) { + case AF_INET: + sin = (struct sockaddr_in *)&rtp_addr; + sin->sin_family = AF_INET; + sin->sin_port = osmo_htons(ac->cn_rtp->port); + if (inet_pton(AF_INET, ac->cn_rtp->ip, &sin->sin_addr) != 1) { + LOG_RAN_A_ENC(log_fi, LOGL_ERROR, + "Assignment Command: Invalid RTP-Address %s\n", + ac->cn_rtp->ip); + return NULL; + } + if (sin->sin_port == 0) { + LOG_RAN_A_ENC(log_fi, LOGL_ERROR, + "Assignment Command: Invalid RTP-Port\n"); + return NULL; + } + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)&rtp_addr; + sin6->sin6_family = AF_INET6; + sin6->sin6_port = osmo_htons(ac->cn_rtp->port); + if (inet_pton(AF_INET6, ac->cn_rtp->ip, &sin6->sin6_addr) != 1) { + LOG_RAN_A_ENC(log_fi, LOGL_ERROR, + "Assignment Command: Invalid RTP-Address %s\n", + ac->cn_rtp->ip); + return NULL; + } + if (sin6->sin6_port == 0) { + LOG_RAN_A_ENC(log_fi, LOGL_ERROR, + "Assignment Command: Invalid RTP-Port\n"); + return NULL; + } + break; + default: + LOG_RAN_A_ENC(log_fi, LOGL_ERROR, + "Assignment Command: Invalid RTP-Address type for %s\n", + ac->cn_rtp->ip); return NULL; } - - memset(&rtp_addr, 0, sizeof(rtp_addr)); - memcpy(&rtp_addr, &rtp_addr_in, sizeof(rtp_addr_in)); - use_rtp_addr = &rtp_addr; } } - msg = gsm0808_create_ass(ac->channel_type, NULL, use_rtp_addr, use_scl, NULL); + if(ac->call_id_present == true) + call_id = &ac->call_id; + + msg = gsm0808_create_ass2(ac->channel_type, NULL, use_rtp_addr, use_scl, call_id, + NULL, ac->lcls); + if (msg == NULL) { + LOG_RAN_A_ENC(log_fi, LOGL_ERROR, + "Failed to encode BSSMAP Assignment Request message\n"); + return NULL; + } + + /* Append optional IEs: Group Call Reference and Osmux CID */ + OSMO_ASSERT(msg->l3h[1] == msgb_l3len(msg) - 2); /* TL not in len */ + if (ac->callref_present) + gsm0808_enc_group_callref(msg, &ac->callref); if (ac->osmux_present) - _gsm0808_assignment_extend_osmux(msg, ac->osmux_cid); + msgb_tv_put(msg, GSM0808_IE_OSMO_OSMUX_CID, ac->osmux_cid); + msg->l3h[1] = msgb_l3len(msg) - 2; return msg; } @@ -988,6 +1485,9 @@ static int a5_n_to_gsm0808_chosen_enc_alg(uint8_t *dst, int a5_n) case 3: *dst = GSM0808_ALG_ID_A5_3; return 0; + case 4: + *dst = GSM0808_ALG_ID_A5_4; + return 0; default: return -ENOTSUP; } @@ -1027,14 +1527,17 @@ osmo_static_assert(sizeof(((struct gsm0808_encrypt_info*)0)->key) >= sizeof(((st gsm0808_encrypt_info_key_fits_osmo_auth_vec_kc); static struct msgb *ran_a_make_cipher_mode_command(struct osmo_fsm_inst *fi, const struct ran_cipher_mode_command *cm) { - struct gsm0808_encrypt_info ei = {}; + struct gsm0808_cipher_mode_command cmc = { + .cipher_response_mode_present = true, + .cipher_response_mode = 1, /* 1: include IMEISV (3GPP TS 48.008 3.2.2.34) */ + }; + struct gsm0808_encrypt_info *ei = &cmc.ei; char buf[16 * 2 + 1]; - const uint8_t cipher_response_mode = 1; - if (make_encrypt_info_perm_algo(fi, &ei, cm->geran.a5_encryption_mask, cm->classmark)) + if (make_encrypt_info_perm_algo(fi, ei, cm->geran.a5_encryption_mask, cm->classmark)) return NULL; - if (ei.perm_algo_len == 0) { + if (ei->perm_algo_len == 0) { LOG_RAN_A_ENC(fi, LOGL_ERROR, "cannot start ciphering, no intersection between MSC-configured" " and MS-supported A5 algorithms. MSC: 0x%02x MS: %s\n", cm->geran.a5_encryption_mask, osmo_gsm48_classmark_a5_name(cm->classmark)); @@ -1044,27 +1547,45 @@ static struct msgb *ran_a_make_cipher_mode_command(struct osmo_fsm_inst *fi, con /* In case of UMTS AKA, the Kc for ciphering must be derived from the 3G auth * tokens. vec->kc was calculated from the GSM algorithm and is not * necessarily a match for the UMTS AKA tokens. */ - if (cm->geran.umts_aka) - osmo_auth_c3(ei.key, cm->vec->ck, cm->vec->ik); - else - memcpy(ei.key, cm->vec->kc, sizeof(cm->vec->kc)); - ei.key_len = sizeof(cm->vec->kc); + if (cm->geran.umts_aka) { + int i; + osmo_auth_c3(ei->key, cm->vec->ck, cm->vec->ik); + + for (i = 0; i < ei->perm_algo_len; i++) { + if (ei->perm_algo[i] != GSM0808_ALG_ID_A5_4) + continue; + /* A5/4 is included, so need to generate Kc128 */ + osmo_kdf_kc128(cm->vec->ck, cm->vec->ik, cmc.kc128); + cmc.kc128_present = true; + break; + } + } else { + memcpy(ei->key, cm->vec->kc, sizeof(cm->vec->kc)); + } + ei->key_len = sizeof(cm->vec->kc); /* Store chosen GERAN key where the caller asked it to be stored. * alg_id remains unknown until we receive a Cipher Mode Complete from the BSC */ if (cm->geran.chosen_key) { - if (ei.key_len > sizeof(cm->geran.chosen_key->key)) { + *cm->geran.chosen_key = (struct geran_encr){0}; + + if (ei->key_len > sizeof(cm->geran.chosen_key->key)) { LOG_RAN_A_ENC(fi, LOGL_ERROR, "Chosen key is larger than I can store\n"); return NULL; } - memcpy(cm->geran.chosen_key->key, ei.key, ei.key_len); - cm->geran.chosen_key->key_len = ei.key_len; + memcpy(cm->geran.chosen_key->key, ei->key, ei->key_len); + cm->geran.chosen_key->key_len = ei->key_len; + + if (cmc.kc128_present) { + memcpy(cm->geran.chosen_key->kc128, cmc.kc128, 16); + cm->geran.chosen_key->kc128_present = true; + } } LOG_RAN_A_ENC(fi, LOGL_DEBUG, "Tx BSSMAP CIPHER MODE COMMAND to BSC, %u ciphers (%s) key %s\n", - ei.perm_algo_len, osmo_hexdump_nospc(ei.perm_algo, ei.perm_algo_len), - osmo_hexdump_buf(buf, sizeof(buf), ei.key, ei.key_len, NULL, false)); - return gsm0808_create_cipher(&ei, cm->geran.retrieve_imeisv ? &cipher_response_mode : NULL); + ei->perm_algo_len, osmo_hexdump_nospc(ei->perm_algo, ei->perm_algo_len), + osmo_hexdump_buf(buf, sizeof(buf), ei->key, ei->key_len, NULL, false)); + return gsm0808_create_cipher2(&cmc); } struct msgb *ran_a_make_handover_request(struct osmo_fsm_inst *log_fi, const struct ran_handover_request *n) @@ -1084,6 +1605,7 @@ struct msgb *ran_a_make_handover_request(struct osmo_fsm_inst *log_fi, const str .imsi = n->imsi, .codec_list_msc_preferred = n->codec_list_msc_preferred, + .call_id_present = n->call_id_present, .call_id = n->call_id, .global_call_reference = n->global_call_reference, .global_call_reference_len = n->global_call_reference_len, @@ -1109,12 +1631,18 @@ struct msgb *ran_a_make_handover_request(struct osmo_fsm_inst *log_fi, const str n->geran.chosen_encryption->key, n->geran.chosen_encryption->key_len); r.encryption_information.key_len = n->geran.chosen_encryption->key_len; r.chosen_encryption_algorithm_serving = n->geran.chosen_encryption->alg_id; + + if (n->geran.chosen_encryption->kc128_present) { + r.more_items = true; + memcpy(r.kc128, n->geran.chosen_encryption->kc128, sizeof(r.kc128)); + r.kc128_present = true; + } } if (n->classmark) r.classmark_information = *n->classmark; - if (osmo_sockaddr_str_is_set(n->rtp_ran_local)) { + if (osmo_sockaddr_str_is_nonzero(n->rtp_ran_local)) { if (osmo_sockaddr_str_to_sockaddr(n->rtp_ran_local, &ss)) { LOG_RAN_A_ENC(log_fi, LOGL_ERROR, "Handover Request: invalid AoIP Transport Layer address/port: " @@ -1139,7 +1667,7 @@ static struct msgb *ran_a_make_handover_request_ack(struct osmo_fsm_inst *caller .chosen_speech_version = r->chosen_speech_version, }; - if (osmo_sockaddr_str_is_set(&r->remote_rtp)) { + if (osmo_sockaddr_str_is_nonzero(&r->remote_rtp)) { osmo_sockaddr_str_to_sockaddr(&r->remote_rtp, &ss); params.aoip_transport_layer = &ss; } @@ -1185,6 +1713,13 @@ static struct msgb *_ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct case RAN_MSG_ASSIGNMENT_COMMAND: return ran_a_make_assignment_command(caller_fi, &ran_enc_msg->assignment_command); + case RAN_MSG_COMMON_ID: + return gsm0808_create_common_id(ran_enc_msg->common_id.imsi, NULL, + ran_enc_msg->common_id.last_eutran_plmn_present ? + &ran_enc_msg->common_id.last_eutran_plmn : + NULL + ); + case RAN_MSG_CIPHER_MODE_COMMAND: return ran_a_make_cipher_mode_command(caller_fi, &ran_enc_msg->cipher_mode_command); @@ -1206,6 +1741,36 @@ static struct msgb *_ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct case RAN_MSG_HANDOVER_FAILURE: return ran_a_make_handover_failure(caller_fi, ran_enc_msg); + case RAN_MSG_VGCS_VBS_SETUP: + return gsm0808_create_vgcs_vbs_setup(&ran_enc_msg->vgcs_vbs_setup); + + case RAN_MSG_VGCS_VBS_ASSIGN_REQ: + return gsm0808_create_vgcs_vbs_assign_req(&ran_enc_msg->vgcs_vbs_assign_req); + + case RAN_MSG_UPLINK_REQUEST_ACK: + return gsm0808_create_uplink_request_ack(&ran_enc_msg->uplink_request_ack); + + case RAN_MSG_UPLINK_REJECT_CMD: + return gsm0808_create_uplink_reject_cmd(&ran_enc_msg->uplink_reject_cmd); + + case RAN_MSG_UPLINK_RELEASE_CMD: + return gsm0808_create_uplink_release_cmd(ran_enc_msg->uplink_release_cmd.cause); + + case RAN_MSG_UPLINK_SEIZED_CMD: + return gsm0808_create_uplink_seized_cmd(&ran_enc_msg->uplink_seized_cmd); + + case RAN_MSG_VGCS_ADDITIONAL_INFO: + return gsm0808_create_vgcs_additional_info(&ran_enc_msg->vgcs_additional_info.talker_identity); + + case RAN_MSG_VGCS_VBS_AREA_CELL_INFO: + return gsm0808_create_vgcs_vbs_area_cell_info(&ran_enc_msg->vgcs_vbs_area_cell_info); + + case RAN_MSG_VGCS_SMS: + return gsm0808_create_vgcs_sms(&ran_enc_msg->vgcs_sms.sms_to_vgcs); + + case RAN_MSG_NOTIFICATION_DATA: + return gsm0808_create_notification_data(&ran_enc_msg->notification_data); + default: LOG_RAN_A_ENC(caller_fi, LOGL_ERROR, "Unimplemented RAN-encode message type: %s\n", ran_msg_type_name(ran_enc_msg->msg_type)); @@ -1237,20 +1802,50 @@ struct msgb *ran_a_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg return msg; } -/* Return 1 for a RESET, 2 for a RESET ACK message, 0 otherwise */ -enum reset_msg_type bssmap_is_reset_msg(const struct sccp_ran_inst *sri, const struct msgb *l2) +static void cl_parse_osmux(struct osmo_fsm_inst *log_fi, struct msgb *msg, int *supports_osmux) +{ + struct tlv_parsed tp; + int rc; + + if (supports_osmux == NULL) + return; + + rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msgb_l3(msg) + 1, msgb_l3len(msg) - 1, 0, 0); + if (rc < 0) { + LOGPFSMSL(log_fi, DBSSAP, LOGL_ERROR, "BSSMAP: Failed parsing TLV looking for Osmux support\n"); + return; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_OSMO_OSMUX_SUPPORT)) { + *supports_osmux = true; + } else { + *supports_osmux = false; + } +} + +/* Return 1 for a RESET, 2 for a RESET ACK message, 0 otherwise. + * In supports_osmux, return 0 for no information, 1 for support detected, -1 for non-support detected. */ +enum reset_msg_type bssmap_is_reset_msg(const struct sccp_ran_inst *sri, struct osmo_fsm_inst *log_fi, + struct msgb *l2, int *supports_osmux) { struct bssmap_header *bs = (struct bssmap_header *)msgb_l2(l2); + if (supports_osmux != NULL) + *supports_osmux = 0; + if (!bs || msgb_l2len(l2) < (sizeof(*bs) + 1) || bs->type != BSSAP_MSG_BSS_MANAGEMENT) return SCCP_RAN_MSG_NON_RESET; - switch (l2->l2h[sizeof(*bs)]) { + l2->l3h = l2->l2h + sizeof(struct bssmap_header); + + switch (l2->l3h[0]) { case BSS_MAP_MSG_RESET: + cl_parse_osmux(log_fi, l2, supports_osmux); return SCCP_RAN_MSG_RESET; case BSS_MAP_MSG_RESET_ACKNOWLEDGE: + cl_parse_osmux(log_fi, l2, supports_osmux); return SCCP_RAN_MSG_RESET_ACK; default: return SCCP_RAN_MSG_NON_RESET; diff --git a/src/libmsc/ran_msg_iu.c b/src/libmsc/ran_msg_iu.c index d5b914379..37cdf1065 100644 --- a/src/libmsc/ran_msg_iu.c +++ b/src/libmsc/ran_msg_iu.c @@ -1,25 +1,21 @@ /* RANAP encoding and decoding for MSC */ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * Author: Neels Hofmeyr * - * SPDX-License-Identifier: GPL-2.0+ + * SPDX-License-Identifier: AGPL-3.0+ * * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * GNU Affero General Public License for more details. */ #include <asn1c/asn1helpers.h> @@ -27,6 +23,7 @@ #include <osmocom/core/prim.h> #include <osmocom/core/byteswap.h> #include <osmocom/crypt/auth.h> +#include <osmocom/crypt/utran_cipher.h> #include <osmocom/gsm/gsm48.h> #include <osmocom/ranap/ranap_common_cn.h> @@ -39,16 +36,6 @@ #include <osmocom/msc/ran_msg_iu.h> #include <osmocom/msc/gsm_04_11.h> -/* Implement the extern talloc_asn1_ctx from libasn1c as talloc ctx for ASN.1 message composition */ -void *talloc_asn1_ctx = NULL; - -/* Implement the extern asn_debug from libasn1c to indicate whether to print asn.1 debug messages. */ -int asn_debug = 0; - -/* Implement the extern asn1_xer_print to indicate whether the ASN.1 binary code decoded and encoded during Iu - * communication should be logged to stderr (see asn.1 generated code in osmo-iuh). */ -int asn1_xer_print = 0; - #define LOG_RAN_IU_DEC(RAN_DEC, level, fmt, args...) \ LOG_RAN_DEC(RAN_DEC, DIUCS, level, "RANAP: " fmt, ## args) @@ -166,7 +153,16 @@ static int ran_iu_decode_rab_assignment_response_decode_setup_ies(struct ran_dec .msg_type = RAN_MSG_ASSIGNMENT_COMPLETE, .msg_name = "RANAP RAB Assignment Response", .assignment_complete = { - .codec = CODEC_AMR_8000_1, + /* For codec compatibility resolution, indicate AMR-FR */ + .codec_present = true, + .codec = { + .fi = true, + .type = GSM0808_SCT_FR3, + .cfg = GSM0808_SC_CFG_DEFAULT_FR_AMR, + }, + /* Indicate that (at least) the first MGW endpoint towards RAN needs to expect VND.3GPP.IUFP + * that encapsulates the AMR-FR RTP payload. */ + .codec_with_iuup = true, }, }; if (osmo_sockaddr_str_from_str(&ran_dec_msg->assignment_complete.remote_rtp, addr, port)) { @@ -221,12 +217,20 @@ success: ranap_free_rab_setupormodifieditemies(&setup_ies); } -static void ran_iu_decode_security_mode_complete(struct ran_dec *ran_iu_decode) +static void ran_iu_decode_security_mode_complete(struct ran_dec *ran_iu_decode, const RANAP_SecurityModeCompleteIEs_t *ies) { struct ran_msg ran_dec_msg = { .msg_type = RAN_MSG_CIPHER_MODE_COMPLETE, .msg_name = "RANAP SecurityModeControl successfulOutcome", + .cipher_mode_complete = { + .utran_integrity = ies->chosenIntegrityProtectionAlgorithm, + .utran_encryption = -1, + }, }; + + if (ies->presenceMask & SECURITYMODECOMPLETEIES_RANAP_CHOSENENCRYPTIONALGORITHM_PRESENT) + ran_dec_msg.cipher_mode_complete.utran_encryption = ies->chosenEncryptionAlgorithm; + ran_decoded(ran_iu_decode, &ran_dec_msg); } @@ -282,7 +286,7 @@ static void ran_iu_decode_ranap_msg(void *_ran_dec, ranap_message *message) case RANAP_ProcedureCode_id_SecurityModeControl: switch (message->direction) { case RANAP_RANAP_PDU_PR_successfulOutcome: - ran_iu_decode_security_mode_complete(ran_iu_decode); + ran_iu_decode_security_mode_complete(ran_iu_decode, &message->msg.securityModeCompleteIEs); return; case RANAP_RANAP_PDU_PR_unsuccessfulOutcome: ran_iu_decode_security_mode_reject(ran_iu_decode); @@ -376,10 +380,21 @@ static struct msgb *ran_iu_make_rab_assignment(struct osmo_fsm_inst *caller_fi, static struct msgb *ran_iu_make_security_mode_command(struct osmo_fsm_inst *caller_fi, const struct ran_cipher_mode_command *cm) { - - LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "Tx RANAP SECURITY MODE COMMAND to RNC, ik %s\n", - osmo_hexdump_nospc(cm->vec->ik, 16)); - return ranap_new_msg_sec_mod_cmd(cm->vec->ik, NULL, RANAP_KeyStatus_new); + /* TODO: make the choice of available UIA algorithms configurable */ + const uint8_t uia_mask = (1 << OSMO_UTRAN_UIA1) | (1 << OSMO_UTRAN_UIA2); + const uint8_t uea_mask = cm->utran.uea_encryption_mask & ~(1 << OSMO_UTRAN_UEA0); + bool use_encryption = uea_mask != 0x00; + + LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "Tx RANAP SECURITY MODE COMMAND to RNC, IK=%s, CK=%s\n", + osmo_hexdump_nospc(cm->vec->ik, 16), + use_encryption ? osmo_hexdump_nospc(cm->vec->ck, 16) : "NONE"); + /* TODO: Do we need to check if the UE supports all of the algorithms and build an intersection like + * in the case of A5? */ + return ranap_new_msg_sec_mod_cmd2(cm->vec->ik, + use_encryption ? cm->vec->ck : NULL, + RANAP_KeyStatus_new, + (uia_mask << 1), /* API treats LSB as UIA0 */ + uea_mask); } @@ -396,25 +411,28 @@ static struct msgb *ran_iu_make_release_command(struct osmo_fsm_inst *caller_fi, struct msgb *ran_iu_encode(struct osmo_fsm_inst *caller_fi, const struct ran_msg *ran_enc_msg) { - LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "%s\n", ran_msg_type_name(ran_enc_msg->msg_type)); - switch (ran_enc_msg->msg_type) { case RAN_MSG_DTAP: + LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "DirectTransfer\n"); return ran_iu_wrap_dtap(ran_enc_msg->dtap); // TODO: RAN_MSG_CLASSMARK_REQUEST ?? case RAN_MSG_CIPHER_MODE_COMMAND: + LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "SecurityModeCommand\n"); return ran_iu_make_security_mode_command(caller_fi, &ran_enc_msg->cipher_mode_command); case RAN_MSG_ASSIGNMENT_COMMAND: + LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "RAB AssignmentRequest\n"); return ran_iu_make_rab_assignment(caller_fi, &ran_enc_msg->assignment_command); case RAN_MSG_COMMON_ID: + LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "CommonId\n"); return ranap_new_msg_common_id(ran_enc_msg->common_id.imsi); case RAN_MSG_CLEAR_COMMAND: + LOG_RAN_IU_ENC(caller_fi, LOGL_DEBUG, "Iu Release\n"); return ran_iu_make_release_command(caller_fi, &ran_enc_msg->clear_command); default: @@ -445,11 +463,15 @@ static void ranap_handle_cl(void *ctx, ranap_message *message) } } -enum reset_msg_type ranap_is_reset_msg(const struct sccp_ran_inst *sri, const struct msgb *l2) +enum reset_msg_type ranap_is_reset_msg(const struct sccp_ran_inst *sri, struct osmo_fsm_inst *log_fi, + struct msgb *l2, int *supports_osmux) { int ret = SCCP_RAN_MSG_NON_RESET; int rc; + if (supports_osmux != NULL) + *supports_osmux = -1; + rc = ranap_cn_rx_cl(ranap_handle_cl, &ret, msgb_l2(l2), msgb_l2len(l2)); if (rc) return 0; diff --git a/src/libmsc/ran_peer.c b/src/libmsc/ran_peer.c index 77740a00b..860444334 100644 --- a/src/libmsc/ran_peer.c +++ b/src/libmsc/ran_peer.c @@ -1,5 +1,5 @@ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -33,6 +33,7 @@ #include <osmocom/msc/vlr.h> #include <osmocom/msc/ran_conn.h> #include <osmocom/msc/cell_id_list.h> +#include <osmocom/msc/msc_vgcs.h> static struct osmo_fsm ran_peer_fsm; @@ -80,13 +81,13 @@ static struct ran_peer *ran_peer_alloc(struct sccp_ran_inst *sri, const struct o struct ran_peer *ran_peer_find_or_create(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr) { - struct ran_peer *rp = ran_peer_find(sri, peer_addr); + struct ran_peer *rp = ran_peer_find_by_addr(sri, peer_addr); if (rp) return rp; return ran_peer_alloc(sri, peer_addr); } -struct ran_peer *ran_peer_find(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr) +struct ran_peer *ran_peer_find_by_addr(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *peer_addr) { struct ran_peer *rp; llist_for_each_entry(rp, &sri->ran_peers, entry) { @@ -118,29 +119,28 @@ void ran_peer_discard_all_conns(struct ran_peer *rp) struct ran_conn *conn, *next; ran_peer_for_each_ran_conn_safe(conn, next, rp) { - ran_conn_discard(conn); + /* Tell VGCS FSM that the connections have been cleared. */ + if (conn->vgcs.bss) + vgcs_vbs_clear_cpl(conn->vgcs.bss, NULL); + else if (conn->vgcs.cell) + vgcs_vbs_clear_cpl_channel(conn->vgcs.cell, NULL); + else ran_conn_discard(conn); } } -/* TODO: create an sccp_ran_ops.rx_reset(_ack) to handle this differently on 2g and 3G */ -/* We expect RAN peer to provide use with an Osmocom extension TLV in BSSMAP_RESET to - * announce Osmux support */ -static void ran_peer_update_osmux_support(struct ran_peer *rp, struct msgb *msg) +static void ran_peer_update_osmux_support(struct ran_peer *rp, int supports_osmux) { - struct tlv_parsed tp; - int rc; bool old_value = rp->remote_supports_osmux; - OSMO_ASSERT(msg); - msg->l3h = msg->l2h + sizeof(struct bssmap_header); - rc = tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l3h + 1, msgb_l3len(msg) - 1, 0, 0); - if (rc < 0) - LOG_RAN_PEER(rp, LOGL_NOTICE, "Failed parsing TLV looking for Osmux support\n"); - - if (TLVP_PRESENT(&tp, GSM0808_IE_OSMO_OSMUX_SUPPORT)) { + switch (supports_osmux) { + case 1: rp->remote_supports_osmux = true; - } else { + break; + case -1: rp->remote_supports_osmux = false; + break; + default: + return; } if (old_value != rp->remote_supports_osmux) @@ -155,8 +155,6 @@ static void ran_peer_rx_reset(struct ran_peer *rp, struct msgb* msg) ran_peer_discard_all_conns(rp); - ran_peer_update_osmux_support(rp, msg); - reset_ack = rp->sri->ran->sccp_ran_ops.make_reset_msg(rp->sri, SCCP_RAN_MSG_RESET_ACK); if (!reset_ack) { @@ -183,7 +181,6 @@ static void ran_peer_rx_reset(struct ran_peer *rp, struct msgb* msg) static void ran_peer_rx_reset_ack(struct ran_peer *rp, struct msgb* msg) { ran_peer_state_chg(rp, RAN_PEER_ST_READY); - ran_peer_update_osmux_support(rp, msg); } void ran_peer_reset(struct ran_peer *rp) @@ -213,14 +210,18 @@ void ran_peer_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *da struct ran_peer *rp = fi->priv; struct ran_peer_ev_ctx *ctx = data; struct msgb *msg = ctx->msg; + enum reset_msg_type is_reset; + int supports_osmux; switch (event) { case RAN_PEER_EV_MSG_UP_CL: - switch (rp->sri->ran->sccp_ran_ops.is_reset_msg(rp->sri, msg)) { - case 1: + is_reset = rp->sri->ran->sccp_ran_ops.is_reset_msg(rp->sri, fi, msg, &supports_osmux); + ran_peer_update_osmux_support(rp, supports_osmux); + switch (is_reset) { + case SCCP_RAN_MSG_RESET: osmo_fsm_inst_dispatch(fi, RAN_PEER_EV_RX_RESET, msg); return; - case 2: + case SCCP_RAN_MSG_RESET_ACK: osmo_fsm_inst_dispatch(fi, RAN_PEER_EV_RX_RESET_ACK, msg); return; default: @@ -398,18 +399,22 @@ void ran_peer_st_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) OSMO_ASSERT(ctx->conn); OSMO_ASSERT(ctx->msg); - if (!ctx->conn->msc_role) { + if (ctx->conn->msc_role) { + /* "normal" A connection, dispatch to MSC-I or MSC-T */ + an_apdu = (struct an_apdu){ + .an_proto = rp->sri->ran->an_proto, + .msg = ctx->msg, + }; + osmo_fsm_inst_dispatch(ctx->conn->msc_role, MSC_EV_FROM_RAN_UP_L2, &an_apdu); + } else if (ctx->conn->vgcs.bss) { + /* VGCS call related */ + msc_a_rx_vgcs_bss(ctx->conn->vgcs.bss, ctx->conn, ctx->msg); + } else if (ctx->conn->vgcs.cell) { + /* VGCS channel related */ + msc_a_rx_vgcs_cell(ctx->conn->vgcs.cell, ctx->conn, ctx->msg); + } else LOG_RAN_PEER(rp, LOGL_ERROR, "Rx CO message on conn that is not associated with any MSC role\n"); - return; - } - - an_apdu = (struct an_apdu){ - .an_proto = rp->sri->ran->an_proto, - .msg = ctx->msg, - }; - - osmo_fsm_inst_dispatch(ctx->conn->msc_role, MSC_EV_FROM_RAN_UP_L2, &an_apdu); return; case RAN_PEER_EV_MSG_DOWN_CO_INITIAL: @@ -629,17 +634,6 @@ struct ran_peer *ran_peer_find_by_cell_id(struct sccp_ran_inst *sri, const struc return found; } -struct ran_peer *ran_peer_find_by_addr(struct sccp_ran_inst *sri, const struct osmo_sccp_addr *addr) -{ - struct ran_peer *rp; - - llist_for_each_entry(rp, &sri->ran_peers, entry) { - if (!osmo_sccp_addr_ri_cmp(addr, &rp->peer_addr)) - return rp; - } - return NULL; -} - int ran_peers_down_paging(struct sccp_ran_inst *sri, enum CELL_IDENT page_where, struct vlr_subscr *vsub, enum paging_cause cause) { @@ -650,7 +644,7 @@ int ran_peers_down_paging(struct sccp_ran_inst *sri, enum CELL_IDENT page_where, switch (page_where) { case CELL_IDENT_NO_CELL: - LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Asked to page on NO_CELL, wich doesn't make sense.\n"); + LOG_SCCP_RAN_CAT(sri, DPAG, LOGL_ERROR, "Asked to page on NO_CELL, which doesn't make sense.\n"); return 0; case CELL_IDENT_UTRAN_PLMN_LAC_RNC: diff --git a/src/libmsc/ran_up_l2.c b/src/libmsc/ran_up_l2.c deleted file mode 100644 index e69de29bb..000000000 --- a/src/libmsc/ran_up_l2.c +++ /dev/null diff --git a/src/libmsc/rtp_stream.c b/src/libmsc/rtp_stream.c index c3880bf7b..eb9ba7e52 100644 --- a/src/libmsc/rtp_stream.c +++ b/src/libmsc/rtp_stream.c @@ -1,5 +1,5 @@ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ @@ -28,6 +28,7 @@ #include <osmocom/msc/transaction.h> #include <osmocom/msc/call_leg.h> #include <osmocom/msc/rtp_stream.h> +#include <osmocom/msc/codec_mapping.h> #define LOG_RTPS(rtps, level, fmt, args...) \ LOGPFSML(rtps->fi, level, fmt, ##args) @@ -74,14 +75,16 @@ void rtp_stream_update_id(struct rtp_stream *rtps) OSMO_STRBUF_PRINTF(sb, ":no-CI"); } else { OSMO_STRBUF_PRINTF(sb, ":CI-%s", osmo_mgcpc_ep_ci_id(rtps->ci)); - if (!osmo_sockaddr_str_is_set(&rtps->remote)) + if (!osmo_sockaddr_str_is_nonzero(&rtps->remote)) OSMO_STRBUF_PRINTF(sb, ":no-remote-port"); else if (!rtps->remote_sent_to_mgw) OSMO_STRBUF_PRINTF(sb, ":remote-port-not-sent"); - if (!rtps->codec_known) - OSMO_STRBUF_PRINTF(sb, ":no-codec"); - else if (!rtps->codec_sent_to_mgw) - OSMO_STRBUF_PRINTF(sb, ":codec-not-sent"); + if (!rtps->codecs_known) + OSMO_STRBUF_PRINTF(sb, ":no-codecs"); + else if (!rtps->codecs_sent_to_mgw) + OSMO_STRBUF_PRINTF(sb, ":codecs-not-sent"); + if (!rtps->codecs_sent_to_mgw) + OSMO_STRBUF_PRINTF(sb, ":mode-not-sent"); if (rtps->use_osmux) { if (rtps->remote_osmux_cid < 0) OSMO_STRBUF_PRINTF(sb, ":no-remote-osmux-cid"); @@ -89,9 +92,9 @@ void rtp_stream_update_id(struct rtp_stream *rtps) OSMO_STRBUF_PRINTF(sb, ":remote-osmux-cid-not-sent"); } } - if (osmo_sockaddr_str_is_set(&rtps->local)) + if (osmo_sockaddr_str_is_nonzero(&rtps->local)) OSMO_STRBUF_PRINTF(sb, ":local-%s-%u", rtps->local.ip, rtps->local.port); - if (osmo_sockaddr_str_is_set(&rtps->remote)) + if (osmo_sockaddr_str_is_nonzero(&rtps->remote)) OSMO_STRBUF_PRINTF(sb, ":remote-%s-%u", rtps->remote.ip, rtps->remote.port); if (rtps->use_osmux) OSMO_STRBUF_PRINTF(sb, ":osmux-%d-%d", rtps->local_osmux_cid, rtps->remote_osmux_cid); @@ -107,13 +110,14 @@ void rtp_stream_update_id(struct rtp_stream *rtps) /* Allocate RTP stream under a call leg. This is one RTP connection from some remote entity with address and port to a * local RTP address and port. call_id is stored for sending in MGCP transactions and as logging context. for_trans is * optional, merely stored for reference by callers, and appears as log context if not NULL. */ -struct rtp_stream *rtp_stream_alloc(struct call_leg *parent_call_leg, enum rtp_direction dir, - uint32_t call_id, struct gsm_trans *for_trans) +struct rtp_stream *rtp_stream_alloc(struct osmo_fsm_inst *parent_fi, uint32_t event_gone, uint32_t event_avail, + uint32_t event_estab, enum rtp_direction dir, uint32_t call_id, + struct gsm_trans *for_trans) { struct osmo_fsm_inst *fi; struct rtp_stream *rtps; - fi = osmo_fsm_inst_alloc_child(&rtp_stream_fsm, parent_call_leg->fi, CALL_LEG_EV_RTP_STREAM_GONE); + fi = osmo_fsm_inst_alloc_child(&rtp_stream_fsm, parent_fi, event_gone); OSMO_ASSERT(fi); rtps = talloc(fi, struct rtp_stream); @@ -121,12 +125,14 @@ struct rtp_stream *rtp_stream_alloc(struct call_leg *parent_call_leg, enum rtp_d fi->priv = rtps; *rtps = (struct rtp_stream){ .fi = fi, - .parent_call_leg = parent_call_leg, + .event_avail = event_avail, + .event_estab = event_estab, .call_id = call_id, .for_trans = for_trans, .dir = dir, .local_osmux_cid = -2, .remote_osmux_cid = -2, + .crcx_conn_mode = MGCP_CONN_NONE, /* Use connection's default mode. */ }; rtp_stream_update_id(rtps); @@ -137,11 +143,11 @@ struct rtp_stream *rtp_stream_alloc(struct call_leg *parent_call_leg, enum rtp_d static void check_established(struct rtp_stream *rtps) { if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED - && osmo_sockaddr_str_is_set(&rtps->local) - && osmo_sockaddr_str_is_set(&rtps->remote) + && osmo_sockaddr_str_is_nonzero(&rtps->local) + && osmo_sockaddr_str_is_nonzero(&rtps->remote) && rtps->remote_sent_to_mgw && (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw) - && rtps->codec_known) + && rtps->codecs_known) rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHED); } @@ -168,17 +174,18 @@ static void rtp_stream_fsm_establishing_established(struct osmo_fsm_inst *fi, ui if (crcx_info->x_osmo_osmux_use) rtps->local_osmux_cid = crcx_info->x_osmo_osmux_cid; rtp_stream_update_id(rtps); - osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ADDR_AVAILABLE, rtps); + osmo_fsm_inst_dispatch(fi->proc.parent, rtps->event_avail, rtps); check_established(rtps); - if ((!rtps->remote_sent_to_mgw || !rtps->codec_sent_to_mgw) - && osmo_sockaddr_str_is_set(&rtps->remote) + if ((!rtps->remote_sent_to_mgw || !rtps->codecs_sent_to_mgw || !rtps->mode_sent_to_mgw) + && osmo_sockaddr_str_is_nonzero(&rtps->remote) && (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw) - && rtps->codec_known) { + && rtps->codecs_known) { LOG_RTPS(rtps, LOGL_DEBUG, - "local ip:port set;%s%s%s triggering MDCX to send the new settings\n", - (!rtps->remote_sent_to_mgw)? " remote ip:port not yet sent," : "", - (!rtps->codec_sent_to_mgw)? " codec not yet sent," : "", + "local ip:port set;%s%s%s%s triggering MDCX to send the new settings\n", + (!rtps->remote_sent_to_mgw) ? " remote ip:port not yet sent," : "", + (!rtps->codecs_sent_to_mgw) ? " codecs not yet sent," : "", + (!rtps->mode_sent_to_mgw) ? " mode not yet sent," : "", (rtps->use_osmux && !rtps->remote_osmux_cid_sent_to_mgw) ? "Osmux CID not yet sent,": ""); rtp_stream_do_mdcx(rtps); } @@ -192,7 +199,8 @@ static void rtp_stream_fsm_establishing_established(struct osmo_fsm_inst *fi, ui case RTP_STREAM_EV_CRCX_FAIL: case RTP_STREAM_EV_MDCX_FAIL: rtps->remote_sent_to_mgw = false; - rtps->codec_sent_to_mgw = false; + rtps->codecs_sent_to_mgw = false; + rtps->mode_sent_to_mgw = false; rtps->remote_osmux_cid_sent_to_mgw = false; rtp_stream_update_id(rtps); rtp_stream_state_chg(rtps, RTP_STREAM_ST_DISCARDING); @@ -206,7 +214,7 @@ static void rtp_stream_fsm_establishing_established(struct osmo_fsm_inst *fi, ui void rtp_stream_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct rtp_stream *rtps = fi->priv; - osmo_fsm_inst_dispatch(fi->proc.parent, CALL_LEG_EV_RTP_STREAM_ESTABLISHED, rtps); + osmo_fsm_inst_dispatch(fi->proc.parent, rtps->event_estab, rtps); } static int rtp_stream_fsm_timer_cb(struct osmo_fsm_inst *fi) @@ -220,6 +228,7 @@ static void rtp_stream_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_ { struct rtp_stream *rtps = fi->priv; if (rtps->ci) { + osmo_mgcpc_ep_cancel_notify(osmo_mgcpc_ep_ci_ep(rtps->ci), fi); osmo_mgcpc_ep_ci_dlcx(rtps->ci); rtps->ci = NULL; } @@ -306,15 +315,32 @@ static int rtp_stream_do_mgcp_verb(struct rtp_stream *rtps, enum mgcp_verb verb, .x_osmo_osmux_cid = rtps->remote_osmux_cid, }; - if (verb == MGCP_VERB_CRCX) - verb_info.conn_mode = rtps->crcx_conn_mode; - - if (rtps->codec_known) { - verb_info.codecs[0] = rtps->codec; - verb_info.codecs_len = 1; - rtps->codec_sent_to_mgw = true; + verb_info.conn_mode = rtps->crcx_conn_mode; + + if (rtps->codecs_known) { + /* Send the list of codecs to the MGW. Ideally we would just feed the SDP directly, but for legacy + * reasons we still need to translate to a struct mgcp_conn_peer representation to send it. */ + struct sdp_audio_codec *codec; + int i = 0; + sdp_audio_codecs_foreach(codec, &rtps->codecs) { + const struct codec_mapping *m = codec_mapping_by_subtype_name(codec->subtype_name); + if (!m) { + LOG_RTPS(rtps, LOGL_ERROR, "Cannot map codec '%s' to MGCP: codec is unknown\n", + codec->subtype_name); + continue; + } + verb_info.codecs[i] = m->mgcp; + verb_info.ptmap[i] = (struct ptmap){ + .codec = m->mgcp, + .pt = codec->payload_type, + }; + i++; + verb_info.codecs_len = i; + verb_info.ptmap_len = i; + } + rtps->codecs_sent_to_mgw = true; } - if (osmo_sockaddr_str_is_set(&rtps->remote)) { + if (osmo_sockaddr_str_is_nonzero(&rtps->remote)) { int rc = osmo_strlcpy(verb_info.addr, rtps->remote.ip, sizeof(verb_info.addr)); if (rc <= 0 || rc >= sizeof(verb_info.addr)) { LOG_RTPS(rtps, LOGL_ERROR, "Failure to write IP address to MGCP message (rc=%d)\n", rc); @@ -323,6 +349,10 @@ static int rtp_stream_do_mgcp_verb(struct rtp_stream *rtps, enum mgcp_verb verb, verb_info.port = rtps->remote.port; rtps->remote_sent_to_mgw = true; } + rtps->mode_sent_to_mgw = true; + if (rtps->use_osmux && rtps->remote_osmux_cid >= 0) + rtps->remote_osmux_cid_sent_to_mgw = true; + rtp_stream_update_id(rtps); osmo_mgcpc_ep_ci_request(rtps->ci, verb, &verb_info, rtps->fi, ok_event, fail_event, NULL); return 0; @@ -356,47 +386,91 @@ void rtp_stream_release(struct rtp_stream *rtps) } /* After setting up a remote RTP address or a new codec, call this to trigger an MDCX. - * The MDCX will only trigger if all data needed by an endpoint is available (both RTP address and codec) and if at + * The MDCX will only trigger if all data needed by an endpoint is available (RTP address, codecs and mode) and if at * least one of them has not yet been sent to the MGW in a previous CRCX or MDCX. */ int rtp_stream_commit(struct rtp_stream *rtps) { - if (!rtps->ci) { - LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no MGW endpoint CI set up\n"); - return -1; - } - if (!osmo_sockaddr_str_is_set(&rtps->remote)) { + if (!osmo_sockaddr_str_is_nonzero(&rtps->remote)) { LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no remote RTP address known\n"); return -1; } - if (!rtps->codec_known) { - LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no codec known\n"); + if (!rtps->codecs_known) { + LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no codecs known\n"); return -1; } - if (rtps->remote_sent_to_mgw && rtps->codec_sent_to_mgw) { - LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: both remote RTP address and codec already set up at MGW\n"); + if (rtps->remote_sent_to_mgw && rtps->codecs_sent_to_mgw && rtps->mode_sent_to_mgw) { + LOG_RTPS(rtps, LOGL_DEBUG, + "Not committing: remote RTP address, codecs and mode are already set up at MGW\n"); return 0; } + if (!rtps->ci) { + LOG_RTPS(rtps, LOGL_DEBUG, "Not committing: no MGW endpoint CI set up\n"); + return -1; + } - LOG_RTPS(rtps, LOGL_DEBUG, "Committing: Tx MDCX to update the MGW: updating%s%s%s\n", + LOG_RTPS(rtps, LOGL_DEBUG, "Committing: Tx MDCX to update the MGW: updating%s%s%s%s\n", rtps->remote_sent_to_mgw ? "" : " remote-RTP-IP-port", - rtps->codec_sent_to_mgw ? "" : " codec", + rtps->codecs_sent_to_mgw ? "" : " codecs", + rtps->mode_sent_to_mgw ? "" : " mode", (!rtps->use_osmux || rtps->remote_osmux_cid_sent_to_mgw) ? "" : " remote-Osmux-CID"); return rtp_stream_do_mdcx(rtps); } -void rtp_stream_set_codec(struct rtp_stream *rtps, enum mgcp_codecs codec) +void rtp_stream_set_codecs(struct rtp_stream *rtps, const struct sdp_audio_codecs *codecs) +{ + if (!codecs || !codecs->count) + return; + if (sdp_audio_codecs_cmp(&rtps->codecs, codecs, false, true) == 0) { + LOG_RTPS(rtps, LOGL_DEBUG, "no change: codecs already set to %s\n", + sdp_audio_codecs_to_str(&rtps->codecs)); + return; + } + if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED) + rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING); + LOG_RTPS(rtps, LOGL_DEBUG, "setting codecs to %s\n", sdp_audio_codecs_to_str(codecs)); + rtps->codecs = *codecs; + rtps->codecs_known = true; + rtps->codecs_sent_to_mgw = false; + rtp_stream_update_id(rtps); +} + +void rtp_stream_set_mode(struct rtp_stream *rtps, enum mgcp_connection_mode mode) { + if (rtps->crcx_conn_mode == mode) + return; if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED) rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING); - LOG_RTPS(rtps, LOGL_DEBUG, "setting codec to %s\n", osmo_mgcpc_codec_name(codec)); - rtps->codec = codec; - rtps->codec_known = true; - rtps->codec_sent_to_mgw = false; + LOG_RTPS(rtps, LOGL_DEBUG, "setting mode to %s\n", mgcp_client_cmode_name(mode)); + rtps->mode_sent_to_mgw = false; + rtps->crcx_conn_mode = mode; rtp_stream_update_id(rtps); } +/* Convenience shortcut to call rtp_stream_set_codecs() with a list of only one sdp_audio_codec record. */ +void rtp_stream_set_one_codec(struct rtp_stream *rtps, const struct sdp_audio_codec *codec) +{ + struct sdp_audio_codecs codecs = {}; + sdp_audio_codecs_add_copy(&codecs, codec); + rtp_stream_set_codecs(rtps, &codecs); +} + +/* For legacy, rather use rtp_stream_set_codecs() with a full codecs list. */ +bool rtp_stream_set_codecs_from_mgcp_codec(struct rtp_stream *rtps, enum mgcp_codecs codec) +{ + struct sdp_audio_codecs codecs = {}; + if (!sdp_audio_codecs_add_mgcp_codec(&codecs, codec)) + return false; + rtp_stream_set_codecs(rtps, &codecs); + return true; +} + void rtp_stream_set_remote_addr(struct rtp_stream *rtps, const struct osmo_sockaddr_str *r) { + if (osmo_sockaddr_str_cmp(&rtps->remote, r) == 0) { + LOG_RTPS(rtps, LOGL_DEBUG, "remote addr already " OSMO_SOCKADDR_STR_FMT ", no change\n", + OSMO_SOCKADDR_STR_FMT_ARGS(r)); + return; + } if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED) rtp_stream_state_chg(rtps, RTP_STREAM_ST_ESTABLISHING); LOG_RTPS(rtps, LOGL_DEBUG, "setting remote addr to " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(r)); @@ -405,6 +479,13 @@ void rtp_stream_set_remote_addr(struct rtp_stream *rtps, const struct osmo_socka rtp_stream_update_id(rtps); } +void rtp_stream_set_remote_addr_and_codecs(struct rtp_stream *rtps, const struct sdp_msg *sdp) +{ + rtp_stream_set_codecs(rtps, &sdp->audio_codecs); + if (osmo_sockaddr_str_is_nonzero(&sdp->rtp)) + rtp_stream_set_remote_addr(rtps, &sdp->rtp); +} + void rtp_stream_set_remote_osmux_cid(struct rtp_stream *rtps, uint8_t osmux_cid) { if (rtps->fi->state == RTP_STREAM_ST_ESTABLISHED) @@ -424,7 +505,8 @@ bool rtp_stream_is_established(struct rtp_stream *rtps) if (rtps->fi->state != RTP_STREAM_ST_ESTABLISHED) return false; if (!rtps->remote_sent_to_mgw - || !rtps->codec_sent_to_mgw + || !rtps->codecs_sent_to_mgw + || !rtps->mode_sent_to_mgw || (rtps->use_osmux && !rtps->remote_osmux_cid_sent_to_mgw)) return false; return true; diff --git a/src/libmsc/sccp_ran.c b/src/libmsc/sccp_ran.c index 99317b500..9907f200d 100644 --- a/src/libmsc/sccp_ran.c +++ b/src/libmsc/sccp_ran.c @@ -1,5 +1,5 @@ /* - * (C) 2019 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de> + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * All Rights Reserved * * SPDX-License-Identifier: AGPL-3.0+ diff --git a/src/libmsc/sdp_msg.c b/src/libmsc/sdp_msg.c new file mode 100644 index 000000000..9b4cd988c --- /dev/null +++ b/src/libmsc/sdp_msg.c @@ -0,0 +1,686 @@ +/* Minimalistic SDP parse/compose implementation, focused on GSM audio codecs */ +/* + * (C) 2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Neels Hofmeyr + * + * 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 <string.h> +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> + +#include <osmocom/msc/debug.h> +#include <osmocom/msc/sdp_msg.h> + +bool sdp_audio_codec_is_set(const struct sdp_audio_codec *a) +{ + return a && a->subtype_name[0]; +} + +/* Compare name, rate and fmtp, returning typical cmp result: 0 on match, and -1 / 1 on mismatch. + * If cmp_fmtp is false, do *not* compare the fmtp string; if true, compare fmtp 1:1 as strings. + * If cmp_payload_type is false, do *not* compare the payload_type number. + * The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even + * though all parameters are the same. */ +int sdp_audio_codec_cmp(const struct sdp_audio_codec *a, const struct sdp_audio_codec *b, + bool cmp_fmtp, bool cmp_payload_type) +{ + int cmp; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + cmp = strncmp(a->subtype_name, b->subtype_name, sizeof(a->subtype_name)); + if (cmp) + return cmp; + cmp = OSMO_CMP(a->rate, b->rate); + if (cmp) + return cmp; + if (cmp_fmtp) { + cmp = strncmp(a->fmtp, b->fmtp, sizeof(a->fmtp)); + if (cmp) + return cmp; + } + if (cmp_payload_type) { + cmp = OSMO_CMP(a->payload_type, b->payload_type); + if (cmp) + return cmp; + } + return 0; +} + +/* Compare two lists of audio codecs, returning typical cmp result: 0 on match, and -1 / 1 on mismatch. + * The ordering in the two lists may differ, except that the first codec in 'a' must also be the first codec in 'b'. + * This is because the first codec typically expresses the preferred codec to use. + * If cmp_fmtp is false, do *not* compare the fmtp strings; if true, compare fmtp 1:1 as strings. + * If cmp_payload_type is false, do *not* compare the payload_type numbers. + * The fmtp is only string-compared -- e.g. if AMR parameters appear in a different order, it amounts to a mismatch even + * though all parameters are the same. */ +int sdp_audio_codecs_cmp(const struct sdp_audio_codecs *a, const struct sdp_audio_codecs *b, + bool cmp_fmtp, bool cmp_payload_type) +{ + const struct sdp_audio_codec *codec_a; + const struct sdp_audio_codec *codec_b; + int cmp; + if (a == b) + return 0; + if (!a) + return -1; + if (!b) + return 1; + + cmp = OSMO_CMP(a->count, b->count); + if (cmp) + return cmp; + + if (!a->count) + return 0; + + /* The first codec is the "chosen" codec and should match. The others may appear in different order. */ + cmp = sdp_audio_codec_cmp(&a->codec[0], &b->codec[0], cmp_fmtp, cmp_payload_type); + if (cmp) + return cmp; + + /* See if each codec in a is also present in b */ + sdp_audio_codecs_foreach(codec_a, a) { + bool match_found = false; + sdp_audio_codecs_foreach(codec_b, b) { + if (!sdp_audio_codec_cmp(codec_a, codec_b, cmp_fmtp, cmp_payload_type)) { + match_found = true; + break; + } + } + if (!match_found) + return -1; + } + + return 0; +} + +/* Given a predefined fixed payload_type number, add an SDP audio codec entry, if not present yet. + * The payload_type must exist in sdp_msg_payload_type_names. + * Return the audio codec created or already existing for this payload type number. + */ +struct sdp_audio_codec *sdp_audio_codecs_add(struct sdp_audio_codecs *ac, unsigned int payload_type, + const char *subtype_name, unsigned int rate, const char *fmtp) +{ + struct sdp_audio_codec *codec; + + /* Does an entry already exist? */ + codec = sdp_audio_codecs_by_payload_type(ac, payload_type, false); + if (codec) { + /* Already exists, sanity check */ + if (!codec->subtype_name[0]) + OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name); + else if (strcmp(codec->subtype_name, subtype_name)) { + /* There already is an entry with this payload_type number but a mismatching subtype_name. That is + * weird, rather abort. */ + return NULL; + } + if (codec->rate != rate + || (fmtp && strcmp(fmtp, codec->fmtp))) { + /* Mismatching details. Rather abort */ + return NULL; + } + return codec; + } + + /* None exists, create codec entry for this payload type number */ + codec = sdp_audio_codecs_by_payload_type(ac, payload_type, true); + /* NULL means unable to add an entry */ + if (!codec) + return NULL; + + OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name); + if (fmtp) + OSMO_STRLCPY_ARRAY(codec->fmtp, fmtp); + codec->rate = rate; + return codec; +} + +struct sdp_audio_codec *sdp_audio_codecs_add_copy(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec) +{ + return sdp_audio_codecs_add(ac, codec->payload_type, codec->subtype_name, codec->rate, + codec->fmtp[0] ? codec->fmtp : NULL); +} + +/* Find or create an entry for the given payload_type number in the given list of codecs. + * If the given payload_type number is already present in ac, return the first matching entry. + * If no such payload_type number is present: a) return NULL if create == false; + * b) If create == true, add a mostly empty codec entry to the end of ac with the given payload_type number, and return + * the created entry. + * If create == true, a NULL return value means that there was no unused entry left in ac to add this payload_type. + */ +struct sdp_audio_codec *sdp_audio_codecs_by_payload_type(struct sdp_audio_codecs *ac, unsigned int payload_type, + bool create) +{ + struct sdp_audio_codec *codec; + sdp_audio_codecs_foreach(codec, ac) { + if (codec->payload_type == payload_type) + return codec; + } + if (!create) + return NULL; + + if (ac->count >= ARRAY_SIZE(ac->codec)) + return NULL; + + codec = &ac->codec[ac->count]; + *codec = (struct sdp_audio_codec){ + .payload_type = payload_type, + .rate = 8000, + }; + ac->count++; + return codec; +} + +/* Return a given sdp_msg's codec entry that matches the subtype_name and rate of the given codec, or NULL if no + * match is found. Comparison is made by sdp_audio_codec_cmp(cmp_payload_type=false). */ +struct sdp_audio_codec *sdp_audio_codecs_by_descr(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec) +{ + struct sdp_audio_codec *i; + sdp_audio_codecs_foreach(i, ac) { + if (!sdp_audio_codec_cmp(i, codec, false, false)) + return i; + } + return NULL; +} + +/* Remove the codec entry pointed at by 'codec'. 'codec' must point at an entry of 'ac'. + * To use any external codec instance, use sdp_audio_codecs_remove(ac, sdp_audio_codecs_by_descr(ac, codec)). + * Return 0 on success, -ENOENT if codec does not point at the sdp->codec array. */ +int sdp_audio_codecs_remove(struct sdp_audio_codecs *ac, const struct sdp_audio_codec *codec) +{ + struct sdp_audio_codec *i; + if ((codec < ac->codec) + || ((codec - ac->codec) >= OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec)))) + return -ENOENT; + + /* Move all following entries one up */ + ac->count--; + sdp_audio_codecs_foreach(i, ac) { + if (i < codec) + continue; + *i = *(i+1); + } + return 0; +} + +static const char * const sdp_mode_str[] = { + [SDP_MODE_UNSET] = "-", + [SDP_MODE_SENDONLY] = "sendonly", + [SDP_MODE_RECVONLY] = "recvonly", + [SDP_MODE_SENDRECV] = "sendrecv", + [SDP_MODE_INACTIVE] = "inactive", +}; + +/* Convert struct sdp_msg to the actual SDP protocol representation */ +int sdp_msg_to_sdp_str_buf(char *dst, size_t dst_size, const struct sdp_msg *sdp) +{ + const struct sdp_audio_codec *codec; + struct osmo_strbuf sb = { .buf = dst, .len = dst_size }; + const char *ip; + char ipv; + + if (!sdp) { + OSMO_STRBUF_PRINTF(sb, "%s", ""); + return sb.chars_needed; + } + + ip = sdp->rtp.ip[0] ? sdp->rtp.ip : "0.0.0.0"; + ipv = (osmo_ip_str_type(ip) == AF_INET6) ? '6' : '4'; + + OSMO_STRBUF_PRINTF(sb, + "v=0\r\n" + "o=OsmoMSC 0 0 IN IP%c %s\r\n" + "s=GSM Call\r\n" + "c=IN IP%c %s\r\n" + "t=0 0\r\n" + "m=audio %d RTP/AVP", + ipv, ip, ipv, ip, + sdp->rtp.port); + + /* Append all payload type numbers to 'm=audio <port> RTP/AVP 3 4 112' line */ + sdp_audio_codecs_foreach(codec, &sdp->audio_codecs) + OSMO_STRBUF_PRINTF(sb, " %d", codec->payload_type); + OSMO_STRBUF_PRINTF(sb, "\r\n"); + + /* Add details for all codecs */ + sdp_audio_codecs_foreach(codec, &sdp->audio_codecs) { + if (!sdp_audio_codec_is_set(codec)) + continue; + OSMO_STRBUF_PRINTF(sb, "a=rtpmap:%d %s/%d\r\n", codec->payload_type, codec->subtype_name, + codec->rate > 0 ? codec->rate : 8000); + if (codec->fmtp[0]) + OSMO_STRBUF_PRINTF(sb, "a=fmtp:%d %s\r\n", codec->payload_type, codec->fmtp); + } + + OSMO_STRBUF_PRINTF(sb, "a=ptime:%d\r\n", sdp->ptime > 0? sdp->ptime : 20); + + if (sdp->mode != SDP_MODE_UNSET && sdp->mode < ARRAY_SIZE(sdp_mode_str)) + OSMO_STRBUF_PRINTF(sb, "a=%s\r\n", sdp_mode_str[sdp->mode]); + + return sb.chars_needed; +} + +/* Return the first line ending (or the end of the string) at or after the given string position. */ +const char *sdp_msg_line_end(const char *src) +{ + const char *line_end = strchr(src, '\r'); + if (!line_end) + line_end = strchr(src, '\n'); + if (!line_end) + line_end = src + strlen(src); + return line_end; +} + +/* parse a line like 'a=rtpmap:0 PCMU/8000', 'a=fmtp:112 octet-align=1; mode-set=4', 'a=ptime:20'. + * The src should point at the character after 'a=', e.g. at the start of 'rtpmap', 'fmtp', 'ptime' + */ +int sdp_parse_attrib(struct sdp_msg *sdp, const char *src) +{ + unsigned int payload_type; + struct sdp_audio_codec *codec; +#define A_RTPMAP "rtpmap:" +#define A_FMTP "fmtp:" +#define A_PTIME "ptime:" +#define A_RTCP "rtcp:" + + if (osmo_str_startswith(src, A_RTPMAP)) { + /* "a=rtpmap:3 GSM/8000" */ + char *audio_name; + unsigned int channels = 1; + if (sscanf(src, A_RTPMAP "%u", &payload_type) != 1) + return -EINVAL; + + audio_name = strchr(src, ' '); + if (!audio_name || audio_name >= sdp_msg_line_end(src)) + return -EINVAL; + + codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true); + if (!codec) + return -ENOSPC; + + if (sscanf(audio_name, " %31[^/]/%u/%u", codec->subtype_name, &codec->rate, &channels) < 1) + return -EINVAL; + + if (channels != 1) + return -ENOTSUP; + } + + else if (osmo_str_startswith(src, A_FMTP)) { + /* "a=fmtp:112 octet-align=1;mode-set=0,1,2,3" */ + char *fmtp_str; + const char *line_end = sdp_msg_line_end(src); + if (sscanf(src, A_FMTP "%u", &payload_type) != 1) + return -EINVAL; + + fmtp_str = strchr(src, ' '); + if (!fmtp_str) + return -EINVAL; + fmtp_str++; + if (fmtp_str >= line_end) + return -EINVAL; + + codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true); + if (!codec) + return -ENOSPC; + + /* (+1 because osmo_strlcpy() interprets it as size including the '\0') */ + osmo_strlcpy(codec->fmtp, fmtp_str, line_end - fmtp_str + 1); + } + + else if (osmo_str_startswith(src, A_PTIME)) { + /* "a=ptime:20" */ + if (sscanf(src, A_PTIME "%u", &sdp->ptime) != 1) + return -EINVAL; + + } + + else if (osmo_str_startswith(src, A_RTCP)) { + /* TODO? */ + } + + else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_SENDRECV])) { + /* "a=sendrecv" */ + sdp->mode = SDP_MODE_SENDRECV; + } + + else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_SENDONLY])) { + /* "a=sendonly" */ + sdp->mode = SDP_MODE_SENDONLY; + } + + else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_RECVONLY])) { + /* "a=recvonly" */ + sdp->mode = SDP_MODE_RECVONLY; + } + + else if (osmo_str_startswith(src, sdp_mode_str[SDP_MODE_INACTIVE])) { + /* "a=inactive" */ + sdp->mode = SDP_MODE_INACTIVE; + } + + return 0; +} + +const struct value_string sdp_msg_payload_type_names[] = { + { 0, "PCMU" }, + { 3, "GSM" }, + { 8, "PCMA" }, + { 18, "G729" }, + { 110, "GSM-EFR" }, + { 111, "GSM-HR-08" }, + { 112, "AMR" }, + { 113, "AMR-WB" }, + {} +}; + +/* Return payload type number matching given string ("AMR", "GSM", ...) or negative if not found. */ +int sdp_subtype_name_to_payload_type(const char *subtype_name) +{ + return get_string_value(sdp_msg_payload_type_names, subtype_name); +} + +/* Parse a line like 'm=audio 16398 RTP/AVP 0 3 8 96 112', starting after the '=' */ +static int sdp_parse_media_description(struct sdp_msg *sdp, const char *src) +{ + unsigned int port; + int i; + const char *payload_type_str; + const char *line_end = sdp_msg_line_end(src); + if (sscanf(src, "audio %u RTP/AVP", &port) < 1) + return -ENOTSUP; + + if (port > 0xffff) + return -EINVAL; + + sdp->rtp.port = port; + + /* skip "audio 12345 RTP/AVP ", i.e. 3 spaces on */ + payload_type_str = src; + for (i = 0; i < 3; i++) { + payload_type_str = strchr(payload_type_str, ' '); + if (!payload_type_str) + return -EINVAL; + while (*payload_type_str == ' ') + payload_type_str++; + if (payload_type_str >= line_end) + return -EINVAL; + } + + /* Parse listing of payload type numbers after "RTP/AVP" */ + while (payload_type_str < line_end) { + unsigned int payload_type; + struct sdp_audio_codec *codec; + const char *subtype_name; + if (sscanf(payload_type_str, "%u", &payload_type) < 1) + return -EINVAL; + + codec = sdp_audio_codecs_by_payload_type(&sdp->audio_codecs, payload_type, true); + if (!codec) + return -ENOSPC; + + /* Fill in subtype name for fixed payload types */ + subtype_name = get_value_string_or_null(sdp_msg_payload_type_names, codec->payload_type); + if (subtype_name) + OSMO_STRLCPY_ARRAY(codec->subtype_name, subtype_name); + + payload_type_str = strchr(payload_type_str, ' '); + if (!payload_type_str) + payload_type_str = line_end; + while (*payload_type_str == ' ') + payload_type_str++; + } + + return 0; +} + +/* parse a line like 'c=IN IP4 192.168.11.151' starting after the '=' */ +static int sdp_parse_connection_info(struct sdp_msg *sdp, const char *src) +{ + char ipv[10]; + char addr_str[INET6_ADDRSTRLEN]; + if (sscanf(src, "IN %s %s", ipv, addr_str) < 2) + return -EINVAL; + + /* supporting only IPv4 */ + if (strcmp(ipv, "IP4")) + return -ENOTSUP; + + osmo_sockaddr_str_from_str(&sdp->rtp, addr_str, sdp->rtp.port); + return 0; +} + +/* Parse SDP string into struct sdp_msg. Return 0 on success, negative on error. */ +int sdp_msg_from_sdp_str(struct sdp_msg *sdp, const char *src) +{ + const char *pos; + *sdp = (struct sdp_msg){}; + + for (pos = src; pos && *pos; pos++) { + char attrib; + int rc = 0; + + if (*pos == '\r' || *pos == '\n') + continue; + + /* Expecting only lines starting with 'X='. Not being too strict about it is probably alright. */ + if (pos[1] != '=') + goto next_line; + + attrib = *pos; + pos += 2; + switch (attrib) { + /* a=... */ + case 'a': + rc = sdp_parse_attrib(sdp, pos); + break; + case 'm': + rc = sdp_parse_media_description(sdp, pos); + break; + case 'c': + rc = sdp_parse_connection_info(sdp, pos); + break; + default: + /* ignore any other parameters */ + break; + } + + if (rc) { + size_t line_len; + const char *line_end = sdp_msg_line_end(pos); + pos -= 2; + line_len = line_end - pos; + switch (rc) { + case -EINVAL: + LOGP(DMNCC, LOGL_ERROR, + "Failed to parse SDP: invalid line: %s\n", osmo_quote_str(pos, line_len)); + break; + case -ENOSPC: + LOGP(DMNCC, LOGL_ERROR, + "Failed to parse SDP: no more space for: %s\n", osmo_quote_str(pos, line_len)); + break; + case -ENOTSUP: + LOGP(DMNCC, LOGL_ERROR, + "Failed to parse SDP: not supported: %s\n", osmo_quote_str(pos, line_len)); + break; + default: + LOGP(DMNCC, LOGL_ERROR, + "Failed to parse SDP: %s\n", osmo_quote_str(pos, line_len)); + break; + } + return rc; + } +next_line: + pos = strstr(pos, "\r\n"); + if (!pos) + break; + } + + return 0; +} + +/* Leave only those codecs in 'ac_dest' that are also present in 'ac_other'. + * The matching is made by sdp_audio_codec_cmp(cmp_payload_type=false), i.e. payload_type numbers are not compared and + * fmtp parameters are compared 1:1 as plain strings. + * If translate_payload_type_numbers has an effect if ac_dest and ac_other have mismatching payload_type numbers for the + * same SDP codec descriptions. If translate_payload_type_numbers is true, take the payload_type numbers from ac_other. + * If false, keep payload_type numbers in ac_dest unchanged. */ +void sdp_audio_codecs_intersection(struct sdp_audio_codecs *ac_dest, const struct sdp_audio_codecs *ac_other, + bool translate_payload_type_numbers) +{ + int i; + for (i = 0; i < ac_dest->count; i++) { + struct sdp_audio_codec *codec = &ac_dest->codec[i]; + struct sdp_audio_codec *other; + OSMO_ASSERT(i < ARRAY_SIZE(ac_dest->codec)); + + other = sdp_audio_codecs_by_descr((struct sdp_audio_codecs *)ac_other, codec); + + if (!other) { + OSMO_ASSERT(sdp_audio_codecs_remove(ac_dest, codec) == 0); + i--; + continue; + } + + /* Doing payload_type number translation of part of the intersection because it makes the algorithm + * simpler: we already know ac_dest is a subset of ac_other, and there is no need to resolve payload + * type number conflicts. */ + if (translate_payload_type_numbers) + codec->payload_type = other->payload_type; + } +} + +/* Make sure the given codec is listed as the first codec. 'codec' must be an actual codec entry of the given audio + * codecs list. */ +void sdp_audio_codecs_select(struct sdp_audio_codecs *ac, struct sdp_audio_codec *codec) +{ + struct sdp_audio_codec tmp; + struct sdp_audio_codec *pos; + OSMO_ASSERT((codec >= ac->codec) + && ((codec - ac->codec) < OSMO_MIN(ac->count, ARRAY_SIZE(ac->codec)))); + + /* Already the first? */ + if (codec == ac->codec) + return; + + tmp = *codec; + for (pos = codec - 1; pos >= ac->codec; pos--) + pos[1] = pos[0]; + + ac->codec[0] = tmp; + return; +} + +/* Short single-line representation of an SDP audio codec, convenient for logging. + * Like "AMR/8000:octet-align=1#122" */ +int sdp_audio_codec_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codec *codec) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + OSMO_STRBUF_PRINTF(sb, "%s", codec->subtype_name); + if (codec->rate != 8000) + OSMO_STRBUF_PRINTF(sb, "/%u", codec->rate); + if (codec->fmtp[0]) + OSMO_STRBUF_PRINTF(sb, ":%s", codec->fmtp); + OSMO_STRBUF_PRINTF(sb, "#%d", codec->payload_type); + return sb.chars_needed; +} + +char *sdp_audio_codec_to_str_c(void *ctx, const struct sdp_audio_codec *codec) +{ + OSMO_NAME_C_IMPL(ctx, 32, "sdp_audio_codec_to_str_c-ERROR", sdp_audio_codec_to_str_buf, codec) +} + +const char *sdp_audio_codec_to_str(const struct sdp_audio_codec *codec) +{ + return sdp_audio_codec_to_str_c(OTC_SELECT, codec); +} + +/* Short single-line representation of a list of SDP audio codecs, convenient for logging */ +int sdp_audio_codecs_to_str_buf(char *buf, size_t buflen, const struct sdp_audio_codecs *ac) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + const struct sdp_audio_codec *codec; + if (!ac->count) + OSMO_STRBUF_PRINTF(sb, "(no-codecs)"); + sdp_audio_codecs_foreach(codec, ac) { + bool first = (codec == ac->codec); + if (!first) + OSMO_STRBUF_PRINTF(sb, ","); + OSMO_STRBUF_APPEND(sb, sdp_audio_codec_to_str_buf, codec); + } + return sb.chars_needed; +} + +char *sdp_audio_codecs_to_str_c(void *ctx, const struct sdp_audio_codecs *ac) +{ + OSMO_NAME_C_IMPL(ctx, 128, "sdp_audio_codecs_to_str_c-ERROR", sdp_audio_codecs_to_str_buf, ac) +} + +const char *sdp_audio_codecs_to_str(const struct sdp_audio_codecs *ac) +{ + return sdp_audio_codecs_to_str_c(OTC_SELECT, ac); +} + +/* Short single-line representation of an SDP message, convenient for logging */ +int sdp_msg_to_str_buf(char *buf, size_t buflen, const struct sdp_msg *sdp) +{ + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + if (!sdp) { + OSMO_STRBUF_PRINTF(sb, "NULL"); + return sb.chars_needed; + } + + OSMO_STRBUF_PRINTF(sb, OSMO_SOCKADDR_STR_FMT, OSMO_SOCKADDR_STR_FMT_ARGS(&sdp->rtp)); + OSMO_STRBUF_PRINTF(sb, "{"); + OSMO_STRBUF_APPEND(sb, sdp_audio_codecs_to_str_buf, &sdp->audio_codecs); + if (sdp->bearer_services.count) { + OSMO_STRBUF_PRINTF(sb, ","); + OSMO_STRBUF_APPEND(sb, csd_bs_list_to_str_buf, &sdp->bearer_services); + } + OSMO_STRBUF_PRINTF(sb, "}"); + return sb.chars_needed; +} + +char *sdp_msg_to_str_c(void *ctx, const struct sdp_msg *sdp) +{ + OSMO_NAME_C_IMPL(ctx, 128, "sdp_msg_to_str_c-ERROR", sdp_msg_to_str_buf, sdp) +} + +const char *sdp_msg_to_str(const struct sdp_msg *sdp) +{ + return sdp_msg_to_str_c(OTC_SELECT, sdp); +} + +void sdp_audio_codecs_set_csd(struct sdp_audio_codecs *ac) +{ + *ac = (struct sdp_audio_codecs){ + .count = 1, + .codec = {{ + .payload_type = 120, + .subtype_name = "CLEARMODE", + .rate = 8000, + }}, + }; +} diff --git a/src/libmsc/sgs_iface.c b/src/libmsc/sgs_iface.c index 5ccded775..a845ab84b 100644 --- a/src/libmsc/sgs_iface.c +++ b/src/libmsc/sgs_iface.c @@ -124,7 +124,7 @@ static void subscr_conn_toss(struct vlr_subscr *vsub) LOG_MSUB(msub, LOGL_ERROR, "Force releasing previous subscriber connection: an SGs connection for this" " subscriber is being initiated\n"); - msc_a_release_mo(msub_msc_a(msub), GSM48_REJECT_CONGESTION); + msc_a_release_mo(msub_msc_a(msub), GSM_CAUSE_AUTH_FAILED); /* TODO: is this strong enough? After this, it should be completely disassociated with this subscriber. */ } @@ -336,7 +336,7 @@ const char *subscr_info(const char *imsi) } /* Comfortable status message generator that also generates some basic - * context-dependent dependand log output */ + * context-dependent log output */ static int sgs_tx_status(struct sgs_connection *sgc, const char *imsi, enum sgsap_sgs_cause cause, struct msgb *msg, int sgsap_iei) { @@ -352,7 +352,7 @@ static int sgs_tx_status(struct sgs_connection *sgc, const char *imsi, enum sgsa LOGSGC_VSUB(sgc, subscr_info(imsi), LOGL_ERROR, "Rx %s with invalid mandatory %s IEI!\n", sgsap_msg_type_name(msg->data[0]), sgsap_iei_name(sgsap_iei)); } else if (cause == SGSAP_SGS_CAUSE_COND_IE_ERROR) { - LOGSGC_VSUB(sgc, subscr_info(imsi), LOGL_ERROR, "Rx %s with errornous conditional %s IEI!\n", + LOGSGC_VSUB(sgc, subscr_info(imsi), LOGL_ERROR, "Rx %s with erroneous conditional %s IEI!\n", sgsap_msg_type_name(msg->data[0]), sgsap_iei_name(sgsap_iei)); } else { LOGSGC_VSUB(sgc, subscr_info(imsi), LOGL_ERROR, "Rx %s failed with cause %s at %s IEI!\n", @@ -364,7 +364,7 @@ static int sgs_tx_status(struct sgs_connection *sgc, const char *imsi, enum sgsa return 0; } -/* Called by VLR via callback, transmits the the location update response or +/* Called by VLR via callback, transmits the location update response or * reject, depending on the outcome of the location update. */ static void sgs_tx_loc_upd_resp_cb(struct sgs_lu_response *response) { @@ -372,8 +372,8 @@ static void sgs_tx_loc_upd_resp_cb(struct sgs_lu_response *response) struct vlr_subscr *vsub = response->vsub; struct sgs_mme_ctx *mme; uint8_t new_id[2 + GSM48_TMSI_LEN]; - uint8_t *new_id_ptr = new_id; - unsigned int new_id_len = 0; + uint8_t *new_id_ptr = NULL; + int new_id_len = 0; uint8_t resp_msg_type; /* Determine message type that is sent next (needed for logging) */ @@ -398,9 +398,19 @@ static void sgs_tx_loc_upd_resp_cb(struct sgs_lu_response *response) /* Handle LU accept/reject */ if (response->accepted) { if (vsub->tmsi_new != GSM_RESERVED_TMSI) { - new_id_len = gsm48_generate_mid_from_tmsi(new_id, vsub->tmsi_new); - new_id_ptr = new_id + 2; - new_id_len -= 2; + struct osmo_mobile_identity tmsi_mi = { + .type = GSM_MI_TYPE_TMSI, + .tmsi = vsub->tmsi_new, + }; + new_id_len = osmo_mobile_identity_encode_buf(new_id, sizeof(new_id), &tmsi_mi, false); + if (new_id_len > 0) { + new_id_ptr = new_id; + } else { + /* Failure to encode the TMSI is not actually possible here, this is just for paranoia + * and coverity scan. */ + new_id_len = 0; + LOGPFSMSL(vsub->sgs_fsm, DMM, LOGL_ERROR, "Cannot encode TMSI Mobile Identity\n"); + } } resp = gsm29118_create_lu_ack(vsub->imsi, &vsub->sgs.lai, new_id_ptr, new_id_len); sgs_tx(mme->conn, resp); @@ -466,9 +476,6 @@ int sgs_iface_tx_paging(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind struct gsm29118_paging_req paging_params; struct sgs_mme_ctx *mme; - LOGP(DMSC, LOGL_NOTICE, "XXXXXXXXXX state == %d conf_by_radio_contact_ind == %d\n", - vsub->sgs_fsm->state, vsub->conf_by_radio_contact_ind); - /* See also: 3GPP TS 29.118, chapter 5.1.2.2 Paging Initiation */ if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && vsub->conf_by_radio_contact_ind == true) { LOGPFSMSL(vsub->sgs_fsm, DPAG, LOGL_ERROR, "Will not Page (conf_by_radio_contact_ind == true)\n"); @@ -486,6 +493,9 @@ int sgs_iface_tx_paging(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind if (vlr_sgs_pag_pend(vsub)) return 0; + LOGMME(mme, LOGL_INFO, "Paging on SGs: %s for %s (conf_by_radio_contact_ind=%d)\n", + vlr_subscr_name(vsub), sgsap_service_ind_name(serv_ind), vsub->conf_by_radio_contact_ind); + memset(&paging_params, 0, sizeof(paging_params)); osmo_strlcpy(paging_params.imsi, vsub->imsi, sizeof(paging_params.imsi)); osmo_strlcpy(paging_params.vlr_name, mme->sgs->cfg.vlr_name, sizeof(paging_params.vlr_name)); @@ -598,12 +608,14 @@ static int sgs_rx_loc_upd_req(struct sgs_connection *sgc, struct msgb *msg, cons char *mme_name; struct vlr_sgs_cfg vlr_sgs_cfg; struct vlr_subscr *vsub; + struct osmo_plmn_id last_eutran_plmn_buf, *last_eutran_plmn = NULL; /* Check for lingering connections */ vsub = vlr_subscr_find_by_imsi(gsm_network->vlr, imsi, __func__); if (vsub) { subscr_conn_toss(vsub); vlr_subscr_put(vsub, __func__); + vsub = NULL; } /* Determine MME-Name */ @@ -629,11 +641,29 @@ static int sgs_rx_loc_upd_req(struct sgs_connection *sgc, struct msgb *msg, cons return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_MISSING_MAND_IE, msg, SGSAP_IE_LAI); gsm48_decode_lai2(gsm48_lai, &new_lai); + /* 3GPP TS 23.272 sec 4.3.3 (CSFB): + * "During the SGs location update procedure, obtaining the last used LTE PLMN ID via TAI" + */ + if (TLVP_PRES_LEN(tp, SGSAP_IE_TAI, 3)) { + last_eutran_plmn = &last_eutran_plmn_buf; + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_TAI), last_eutran_plmn); + /* TODO: we could also gather the TAC from here, but we don't need it yet */ + } else if (TLVP_PRES_LEN(tp, SGSAP_IE_EUTRAN_CGI, 3)) { + /* Since TAI is optional, let's try harder getting Last Used + * E-UTRAN PLMN ID by fetching it from E-UTRAN CGI */ + last_eutran_plmn = &last_eutran_plmn_buf; + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_EUTRAN_CGI), last_eutran_plmn); + /* TODO: we could also gather the ECI from here, but we don't need it yet */ + } else { + LOGSGC(sgc, LOGL_INFO, "Receiving SGsAP-LOCATION-UPDATE-REQUEST without TAI nor " + "E-CGI IEs, fast fallback GERAN->EUTRAN won't be possible!\n"); + } + /* Perform actual location update */ memcpy(vlr_sgs_cfg.timer, sgc->sgs->cfg.timer, sizeof(vlr_sgs_cfg.timer)); memcpy(vlr_sgs_cfg.counter, sgc->sgs->cfg.counter, sizeof(vlr_sgs_cfg.counter)); rc = vlr_sgs_loc_update(gsm_network->vlr, &vlr_sgs_cfg, sgs_tx_loc_upd_resp_cb, sgs_iface_tx_paging, - sgs_tx_mm_info_cb, mme_name, type, imsi, &new_lai); + sgs_tx_mm_info_cb, mme_name, type, imsi, &new_lai, last_eutran_plmn); if (rc != 0) { resp = gsm29118_create_lu_rej(imsi, SGSAP_SGS_CAUSE_IMSI_UNKNOWN, NULL); sgs_tx(sgc, resp); @@ -860,7 +890,7 @@ static int sgs_rx_ul_ud(struct sgs_connection *sgc, struct msgb *msg, const stru vlr_subscr_put(vsub, __func__); /* If we do not find an existing connection and allocating a new one - * faild, give up and return status. */ + * failed, give up and return status. */ if (!msc_a) return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_MSG_INCOMP_STATE, msg, 0); @@ -885,6 +915,8 @@ static int sgs_rx_ul_ud(struct sgs_connection *sgc, struct msgb *msg, const stru static int sgs_rx_csfb_ind(struct sgs_connection *sgc, struct msgb *msg, const struct tlv_parsed *tp, char *imsi) { struct vlr_subscr *vsub; + struct osmo_plmn_id last_eutran_plmn_buf; + const struct osmo_plmn_id *last_eutran_plmn = &last_eutran_plmn_buf; /* The MME informs us with this message that the UE has initiated a * service request for MO CS fallback. There is not much we can do with @@ -895,6 +927,26 @@ static int sgs_rx_csfb_ind(struct sgs_connection *sgc, struct msgb *msg, const s if (!vsub) return sgs_tx_status(sgc, imsi, SGSAP_SGS_CAUSE_IMSI_UNKNOWN, msg, SGSAP_IE_IMSI); + /* 3GPP TS 23.272 sec 4.3.3 (CSFB): + * "During the SGs location update procedure, obtaining the last used LTE PLMN ID via TAI" + */ + if (TLVP_PRES_LEN(tp, SGSAP_IE_TAI, 3)) { + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_TAI), &last_eutran_plmn_buf); + /* TODO: we could also gather the TAC from here, but we don't need it yet */ + } else if (TLVP_PRES_LEN(tp, SGSAP_IE_EUTRAN_CGI, 3)) { + /* Since TAI is optional, let's try harder getting Last Used + * E-UTRAN PLMN ID by fetching it from E-UTRAN CGI */ + osmo_plmn_from_bcd(TLVP_VAL(tp, SGSAP_IE_EUTRAN_CGI), &last_eutran_plmn_buf); + /* TODO: we could also gather the ECI from here, but we don't need it yet */ + } else { + LOGSGC(sgc, LOGL_INFO, "Receiving SGsAP-MO-CSFB-INDICATION without TAI nor " + "E-CGI IEs, and they are not known from previous SGsAP-LOCATION-UPDATE-REQUEST. " + "Fast fallback GERAN->EUTRAN won't be possible!\n"); + last_eutran_plmn = NULL; + } + + vlr_subscr_set_last_used_eutran_plmn_id(vsub, last_eutran_plmn); + /* Check for lingering connections */ subscr_conn_toss(vsub); @@ -958,16 +1010,20 @@ int sgs_iface_rx(struct sgs_connection *sgc, struct msgb *msg) } if (TLVP_PRESENT(&tp, SGSAP_IE_IMSI)) { - gsm48_mi_to_string(imsi, sizeof(imsi), TLVP_VAL(&tp, SGSAP_IE_IMSI), TLVP_LEN(&tp, SGSAP_IE_IMSI)); - if (strlen(imsi) < GSM23003_IMSI_MIN_DIGITS) { + struct osmo_mobile_identity mi; + if (osmo_mobile_identity_decode(&mi, + TLVP_VAL(&tp, SGSAP_IE_IMSI), + TLVP_LEN(&tp, SGSAP_IE_IMSI), false) + || mi.type != GSM_MI_TYPE_IMSI) { TX_STATUS_AND_LOG(sgc, msg_type, SGSAP_SGS_CAUSE_INVALID_MAND_IE, - "SGsAP Message %s with short IMSI, dropping\n"); + "SGsAP Message %s with invalid IMSI, dropping\n"); goto error; } + OSMO_STRLCPY_ARRAY(imsi, mi.imsi); } - /* Some messages contain an MME-NAME as mandatore IE, parse it right here. The - * MME-NAME is als immediately registered with the sgc, so it will be implicitly + /* Some messages contain an MME-NAME as mandatory IE, parse it right here. The + * MME-NAME is also immediately registered with the sgc, so it will be implicitly * known to all functions that have access to the sgc context. */ if (!TLVP_PRESENT(&tp, SGSAP_IE_MME_NAME) && (msg_type == SGSAP_MSGT_RESET_IND || msg_type == SGSAP_MSGT_RESET_ACK @@ -1278,7 +1334,7 @@ void sgs_iface_tx_serv_abrt(struct vlr_subscr *vsub) sgs_tx(mme->conn, msg_sgs); } -/*! initalize SGs new interface +/*! initialize SGs new interface * \param[in] ctx talloc context * \param[in] network associated gsm network * \returns returns allocated sgs_stae, NULL in case of error. */ diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c index 3b95a901f..4de12b9e2 100644 --- a/src/libmsc/silent_call.c +++ b/src/libmsc/silent_call.c @@ -140,7 +140,11 @@ int gsm_silent_call_start(struct vlr_subscr *vsub, struct vty *vty) { struct gsm_network *net = vsub->vlr->user_ctx; - struct gsm_trans *trans = trans_alloc(net, vsub, TRANS_SILENT_CALL, 0, 0); + struct gsm_trans *trans; + + trans = trans_alloc(net, vsub, TRANS_SILENT_CALL, 0, 0); + if (trans == NULL) + return -ENODEV; trans->silent_call.ct = *ct; if (traffic_dst_ip) { diff --git a/src/libmsc/smpp_smsc.h b/src/libmsc/smpp_smsc.h deleted file mode 100644 index b26d01126..000000000 --- a/src/libmsc/smpp_smsc.h +++ /dev/null @@ -1,168 +0,0 @@ -#ifndef _SMPP_SMSC_H -#define _SMPP_SMSC_H - -#include <sys/socket.h> -#include <netinet/in.h> - -#include <osmocom/core/utils.h> -#include <osmocom/core/msgb.h> -#include <osmocom/core/write_queue.h> -#include <osmocom/core/timer.h> - -#include <smpp34.h> -#include <smpp34_structs.h> -#include <smpp34_params.h> - -#define SMPP_SYS_ID_LEN 15 -#define SMPP_PASSWD_LEN 8 - -#define MODE_7BIT 7 -#define MODE_8BIT 8 - -struct msc_a; - -enum esme_read_state { - READ_ST_IN_LEN = 0, - READ_ST_IN_MSG = 1, -}; - -struct osmo_smpp_acl; - -struct osmo_smpp_addr { - uint8_t ton; - uint8_t npi; - char addr[21+1]; -}; - -struct osmo_esme { - struct llist_head list; - struct smsc *smsc; - struct osmo_smpp_acl *acl; - int use; - - struct llist_head smpp_cmd_list; - - uint32_t own_seq_nr; - - struct osmo_wqueue wqueue; - struct sockaddr_storage sa; - socklen_t sa_len; - - enum esme_read_state read_state; - uint32_t read_len; - uint32_t read_idx; - struct msgb *read_msg; - - uint8_t smpp_version; - char system_id[SMPP_SYS_ID_LEN+1]; - - uint8_t bind_flags; -}; - -struct osmo_smpp_acl { - struct llist_head list; - struct smsc *smsc; - struct osmo_esme *esme; - char *description; - char system_id[SMPP_SYS_ID_LEN+1]; - char passwd[SMPP_PASSWD_LEN+1]; - int default_route; - int deliver_src_imsi; - int osmocom_ext; - int dcs_transparent; - int alert_notifications; - struct llist_head route_list; -}; - -enum osmo_smpp_rtype { - SMPP_ROUTE_NONE, - SMPP_ROUTE_PREFIX, -}; - -struct osmo_smpp_route { - struct llist_head list; /*!< in acl.route_list */ - struct llist_head global_list; /*!< in smsc->route_list */ - struct osmo_smpp_acl *acl; - enum osmo_smpp_rtype type; - union { - struct osmo_smpp_addr prefix; - } u; -}; - -struct osmo_smpp_cmd { - struct llist_head list; - struct vlr_subscr *vsub; - uint32_t sequence_nr; - uint32_t gsm411_msg_ref; - uint8_t gsm411_trans_id; - bool is_report; - struct osmo_timer_list response_timer; -}; - -struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, - uint32_t sequence_number); -void smpp_cmd_ack(struct osmo_smpp_cmd *cmd); -void smpp_cmd_err(struct osmo_smpp_cmd *cmd, uint32_t status); -void smpp_cmd_flush_pending(struct osmo_esme *esme); - -struct smsc { - struct osmo_fd listen_ofd; - struct llist_head esme_list; - struct llist_head acl_list; - struct llist_head route_list; - const char *bind_addr; - uint16_t listen_port; - char system_id[SMPP_SYS_ID_LEN+1]; - int accept_all; - int smpp_first; - struct osmo_smpp_acl *def_route; - void *priv; -}; - -int smpp_addr_eq(const struct osmo_smpp_addr *a, - const struct osmo_smpp_addr *b); - -struct smsc *smpp_smsc_alloc_init(void *ctx); -int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port); -int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port); -int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port); -void smpp_smsc_stop(struct smsc *smsc); - -void smpp_esme_get(struct osmo_esme *esme); -void smpp_esme_put(struct osmo_esme *esme); - -int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **emse); - -struct osmo_smpp_acl *smpp_acl_alloc(struct smsc *smsc, const char *sys_id); -struct osmo_smpp_acl *smpp_acl_by_system_id(struct smsc *smsc, - const char *sys_id); -void smpp_acl_delete(struct osmo_smpp_acl *acl); - -int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, - uint32_t command_status, char *msg_id); - -int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi, - const char *addr, uint8_t avail_status); - -int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver); - -int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit, - struct submit_sm_resp_t *submit_r); - -int smpp_route_pfx_add(struct osmo_smpp_acl *acl, - const struct osmo_smpp_addr *pfx); -int smpp_route_pfx_del(struct osmo_smpp_acl *acl, - const struct osmo_smpp_addr *pfx); - -int smpp_vty_init(void); - -int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode); - - - -struct gsm_sms; -struct ran_conn; - -bool smpp_route_smpp_first(); -int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a); -#endif diff --git a/src/libmsc/smpp_utils.c b/src/libmsc/smpp_utils.c deleted file mode 100644 index 7fffdd27a..000000000 --- a/src/libmsc/smpp_utils.c +++ /dev/null @@ -1,61 +0,0 @@ - -/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org> - * - * All Rights Reserved - * - * 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 "smpp_smsc.h" -#include <osmocom/core/logging.h> - -int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode) -{ - if ((dcs & 0xF0) == 0xF0) { - if (dcs & 0x04) { - /* bit 2 == 1: 8bit data */ - *data_coding = 0x02; - *mode = MODE_8BIT; - } else { - /* bit 2 == 0: default alphabet */ - *data_coding = 0x01; - *mode = MODE_7BIT; - } - } else if ((dcs & 0xE0) == 0) { - switch (dcs & 0xC) { - case 0: - *data_coding = 0x01; - *mode = MODE_7BIT; - break; - case 4: - *data_coding = 0x02; - *mode = MODE_8BIT; - break; - case 8: - *data_coding = 0x08; /* UCS-2 */ - *mode = MODE_8BIT; - break; - default: - goto unknown_mo; - } - } else { -unknown_mo: - LOGP(DLSMS, LOGL_ERROR, "SMPP MO Unknown Data Coding 0x%02x\n", dcs); - return -1; - } - - return 0; - -} diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c index d60cb4a01..9f18f4feb 100644 --- a/src/libmsc/sms_queue.c +++ b/src/libmsc/sms_queue.c @@ -1,4 +1,4 @@ -/* SMS queue to continously attempt to deliver SMS */ +/* SMS queue to continuously attempt to deliver SMS */ /* * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org> * All Rights Reserved @@ -40,38 +40,113 @@ #include <osmocom/msc/vlr.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stat_item.h> #include <osmocom/vty/vty.h> -/* - * One pending SMS that we wait for. - */ +enum smsq_stat_item_idx { + SMSQ_STAT_SMS_RAM_PENDING, +}; + +static const struct osmo_stat_item_desc smsq_stat_item_desc[] = { + [SMSQ_STAT_SMS_RAM_PENDING] = { "ram:pending", + "Number of SMSs in the in-RAM pending delivery queue" }, +}; + +static const struct osmo_stat_item_group_desc smsq_statg_desc = { + "sms_queue", + "SMS queue", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(smsq_stat_item_desc), + smsq_stat_item_desc, +}; + +enum smsq_rate_ctr_idx { + SMSQ_CTR_SMS_DELIVERY_ATTEMPTS, + SMSQ_CTR_SMS_DELIVERY_ACK, + SMSQ_CTR_SMS_DELIVERY_ERR, + SMSQ_CTR_SMS_DELIVERY_NOMEM, + SMSQ_CTR_SMS_DELIVERY_TIMEOUT, +}; + +static const struct rate_ctr_desc smsq_ctr_desc[] = { + [SMSQ_CTR_SMS_DELIVERY_ATTEMPTS] = { "delivery:attempts", + "Attempted MT SMS deliveries to subscriber" }, + [SMSQ_CTR_SMS_DELIVERY_ACK] = { "deliver:ack", + "Successful MT SMS delivery to subscriber" }, + [SMSQ_CTR_SMS_DELIVERY_ERR] = { "deliver:error", + "Erroneous MT SMS delivery" }, + [SMSQ_CTR_SMS_DELIVERY_NOMEM] = { "deliver:no_memory", + "Failed MT SMS delivery due to no memory on MS" }, + [SMSQ_CTR_SMS_DELIVERY_TIMEOUT] = { "deliver:paging_timeout", + "Failed MT SMS delivery due to paging timeout (MS gone?)" }, +}; + +static const struct rate_ctr_group_desc smsq_ctrg_desc = { + "sms_queue", + "SMS queue", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(smsq_ctr_desc), + smsq_ctr_desc, +}; + +#define smsq_rate_ctr_inc(smsq, idx) \ + rate_ctr_inc(rate_ctr_group_get_ctr((smsq)->ctrg, idx)) +#define smsq_rate_ctr_add(smsq, idx, val) \ + rate_ctr_add(rate_ctr_group_get_ctr((smsq)->ctrg, idx), val) + +#define smsq_stat_item_inc(smsq, idx) \ + osmo_stat_item_inc(osmo_stat_item_group_get_item((smsq)->statg, idx), 1) +#define smsq_stat_item_dec(smsq, idx) \ + osmo_stat_item_dec(osmo_stat_item_group_get_item((smsq)->statg, idx), 1) +#define smsq_stat_item_set(smsq, idx, val) \ + osmo_stat_item_set(osmo_stat_item_group_get_item((smsq)->statg, idx), val) + + +/* One in-RAM record of a "pending SMS". This is not the SMS itself, but merely + * a pointer to the database record. It holds a reference on the vlr_subscriber + * and some counters. While this object exists in RAM, we are regularly attempting + * to deliver the related SMS. */ struct gsm_sms_pending { - struct llist_head entry; + struct llist_head entry; /* gsm_sms_queue.pending_sms */ - struct vlr_subscr *vsub; - struct msc_a *msc_a; - unsigned long long sms_id; - int failed_attempts; - int resend; + struct vlr_subscr *vsub; /* destination subscriber for this SMS */ + struct msc_a *msc_a; /* MSC_A associated with this SMS */ + unsigned long long sms_id; /* unique ID (in SQL database) of this SMS */ + int failed_attempts; /* count of failed deliver attempts so far */ + int resend; /* should we try re-sending it (now) ? */ }; +/* (global) state of the SMS queue. */ struct gsm_sms_queue { - struct osmo_timer_list resend_pending; - struct osmo_timer_list push_queue; + struct osmo_timer_list resend_pending; /* timer triggering sms_resend_pending() */ + struct osmo_timer_list push_queue; /* timer triggering sms_submit_pending() */ struct gsm_network *network; - int max_fail; - int max_pending; - int pending; - - struct llist_head pending_sms; + struct llist_head pending_sms; /* list of gsm_sms_pending */ + struct sms_queue_config *cfg; + int pending; /* current number of gsm_sms_pending in RAM */ + /* last MSISDN for which we read SMS from the database and created gsm_sms_pending records */ char last_msisdn[GSM23003_MSISDN_MAX_DIGITS+1]; + + /* statistics / counters */ + struct osmo_stat_item_group *statg; + struct rate_ctr_group *ctrg; }; +/* private wrapper function to make sure we count all SMS delivery attempts */ +static void _gsm411_send_sms(struct gsm_network *net, struct vlr_subscr *vsub, struct gsm_sms *sms) +{ + smsq_rate_ctr_inc(net->sms_queue, SMSQ_CTR_SMS_DELIVERY_ATTEMPTS); + gsm411_send_sms(net, vsub, sms); +} + static int sms_subscr_cb(unsigned int, unsigned int, void *, void *); static int sms_sms_cb(unsigned int, unsigned int, void *, void *); +/* look-up a 'gsm_sms_pending' for the given sms_id; return NULL if none */ static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq, unsigned long long sms_id) { @@ -85,11 +160,13 @@ static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq, return NULL; } +/* do we currently have a gsm_sms_pending object for the given SMS id? */ int sms_queue_sms_is_pending(struct gsm_sms_queue *smsq, unsigned long long sms_id) { return sms_find_pending(smsq, sms_id) != NULL; } +/* find the first pending SMS (in RAM) for the given subscriber */ static struct gsm_sms_pending *sms_subscriber_find_pending( struct gsm_sms_queue *smsq, struct vlr_subscr *vsub) @@ -104,12 +181,14 @@ static struct gsm_sms_pending *sms_subscriber_find_pending( return NULL; } +/* do we have any pending SMS (in RAM) for the given subscriber? */ static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq, struct vlr_subscr *vsub) { return sms_subscriber_find_pending(smsq, vsub) != NULL; } +/* allocate a new gsm_sms_pending record and fill it with information from 'sms' */ static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq, struct gsm_sms *sms) { @@ -122,16 +201,26 @@ static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq, vlr_subscr_get(sms->receiver, VSUB_USE_SMS_PENDING); pending->vsub = sms->receiver; pending->sms_id = sms->id; + llist_add_tail(&pending->entry, &smsq->pending_sms); + + smsq->pending += 1; + smsq_stat_item_inc(smsq, SMSQ_STAT_SMS_RAM_PENDING); + return pending; } -static void sms_pending_free(struct gsm_sms_pending *pending) +/* release a gsm_sms_pending object */ +static void sms_pending_free(struct gsm_sms_queue *smsq, struct gsm_sms_pending *pending) { + smsq->pending -= 1; + smsq_stat_item_dec(smsq, SMSQ_STAT_SMS_RAM_PENDING); vlr_subscr_put(pending->vsub, VSUB_USE_SMS_PENDING); llist_del(&pending->entry); talloc_free(pending); } +/* this sets the 'resend' flag of the gsm_sms_pending and schedules + * the timer for re-sending */ static void sms_pending_resend(struct gsm_sms_pending *pending) { struct gsm_network *net = pending->vsub->vlr->user_ctx; @@ -148,6 +237,8 @@ static void sms_pending_resend(struct gsm_sms_pending *pending) osmo_timer_schedule(&smsq->resend_pending, 1, 0); } +/* call-back when a pending SMS has failed; try another re-send if number of + * attempts is < smsq->max_fail */ static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error) { struct gsm_network *net = pending->vsub->vlr->user_ctx; @@ -158,17 +249,16 @@ static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error pending->sms_id, pending->failed_attempts); smsq = net->sms_queue; - if (pending->failed_attempts < smsq->max_fail) + if (pending->failed_attempts < smsq->cfg->max_fail) return sms_pending_resend(pending); - sms_pending_free(pending); - smsq->pending -= 1; + sms_pending_free(smsq, pending); } -/* - * Resend all SMS that are scheduled for a resend. This is done to - * avoid an immediate failure. - */ +/* Resend all SMS that are scheduled for a resend. This is done to + * avoid an immediate failure. This iterates over all the (in RAM) + * pending_sms records, checks for resend == true, reads them from the + * DB and attempts to send them via _gsm411_send_sms() */ static void sms_resend_pending(void *_data) { struct gsm_sms_pending *pending, *tmp; @@ -183,12 +273,11 @@ static void sms_resend_pending(void *_data) /* the sms is gone? Move to the next */ if (!sms) { - sms_pending_free(pending); - smsq->pending -= 1; + sms_pending_free(smsq, pending); sms_queue_trigger(smsq); } else { pending->resend = 0; - gsm411_send_sms(smsq->network, sms->receiver, sms); + _gsm411_send_sms(smsq->network, sms->receiver, sms); } } } @@ -244,14 +333,14 @@ struct gsm_sms *smsq_take_next_sms(struct gsm_network *net, return NULL; } -/** - * I will submit up to max_pending - pending SMS to the - * subsystem. - */ +/* read up to 'max_pending' pending SMS from the database and add them to the in-memory + * sms_queue; trigger the first delivery attempt. 'submit' in this context means + * "read from the database and add to the in-memory gsm_sms_queue" and is not to be + * confused with the SMS SUBMIT operation a MS performs when sending a MO-SMS. */ static void sms_submit_pending(void *_data) { struct gsm_sms_queue *smsq = _data; - int attempts = smsq->max_pending - smsq->pending; + int attempts = smsq->cfg->max_pending - smsq->pending; int initialized = 0; unsigned long long first_sub = 0; int attempted = 0, rounds = 0; @@ -272,7 +361,7 @@ static void sms_submit_pending(void *_data) } rounds += 1; - LOGP(DLSMS, LOGL_DEBUG, "Sending SMS round %d\n", rounds); + LOGP(DLSMS, LOGL_DEBUG, "Checking whether to send SMS %llu\n", sms->id); /* * This code needs to detect a loop. It assumes that no SMS @@ -309,6 +398,7 @@ static void sms_submit_pending(void *_data) continue; } + /* allocate a new gsm_sms_pending object in RAM */ pending = sms_pending_from(smsq, sms); if (!pending) { LOGP(DLSMS, LOGL_ERROR, @@ -318,17 +408,16 @@ static void sms_submit_pending(void *_data) } attempted += 1; - smsq->pending += 1; - llist_add_tail(&pending->entry, &smsq->pending_sms); - gsm411_send_sms(smsq->network, sms->receiver, sms); + _gsm411_send_sms(smsq->network, sms->receiver, sms); } while (attempted < attempts && rounds < 1000); LOGP(DLSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds); } -/** - * Send the next SMS or trigger the queue - */ +/* obtain the next pending SMS for given subscriber from database, + * create gsm_sms_pending object and attempt first delivery. If there + * are no SMS pending for the given subscriber, call sms_submit_pending() + * to read more SMS (for any subscriber) into the in-RAM pending queue */ static void sms_send_next(struct vlr_subscr *vsub) { struct gsm_network *net = vsub->vlr->user_ctx; @@ -340,7 +429,7 @@ static void sms_send_next(struct vlr_subscr *vsub) OSMO_ASSERT(!sms_subscriber_is_pending(smsq, vsub)); /* check for more messages for this subscriber */ - sms = db_sms_get_unsent_for_subscr(vsub, UINT_MAX); + sms = db_sms_get_unsent_for_subscr(vsub, INT_MAX); if (!sms) goto no_pending_sms; @@ -356,9 +445,7 @@ static void sms_send_next(struct vlr_subscr *vsub) goto no_pending_sms; } - smsq->pending += 1; - llist_add_tail(&pending->entry, &smsq->pending_sms); - gsm411_send_sms(smsq->network, sms->receiver, sms); + _gsm411_send_sms(smsq->network, sms->receiver, sms); return; no_pending_sms: @@ -366,9 +453,7 @@ no_pending_sms: sms_submit_pending(net->sms_queue); } -/* - * Kick off the queue again. - */ +/* Trigger a call to sms_submit_pending() in one second */ int sms_queue_trigger(struct gsm_sms_queue *smsq) { LOGP(DLSMS, LOGL_DEBUG, "Triggering SMS queue\n"); @@ -379,7 +464,26 @@ int sms_queue_trigger(struct gsm_sms_queue *smsq) return 0; } -int sms_queue_start(struct gsm_network *network, int max_pending) +/* allocate + initialize SMS queue configuration with some default values */ +struct sms_queue_config *sms_queue_cfg_alloc(void *ctx) +{ + struct sms_queue_config *sqcfg = talloc_zero(ctx, struct sms_queue_config); + OSMO_ASSERT(sqcfg); + + sqcfg->max_pending = 20; + sqcfg->max_fail = 1; + sqcfg->delete_delivered = true; + sqcfg->delete_expired = true; + sqcfg->default_validity_mins = 7 * 24 * 60; /* 7 days */ + sqcfg->minimum_validity_mins = 1; + sqcfg->db_file_path = talloc_strdup(ctx, SMS_DEFAULT_DB_FILE_PATH); + + return sqcfg; +} + +/* initialize the sms_queue subsystem and read the first batch of SMS from + * the database for delivery */ +int sms_queue_start(struct gsm_network *network) { struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue); if (!sms) { @@ -387,22 +491,48 @@ int sms_queue_start(struct gsm_network *network, int max_pending) return -1; } - osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network); - osmo_signal_register_handler(SS_SMS, sms_sms_cb, network); + sms->cfg = network->sms_queue_cfg; + sms->statg = osmo_stat_item_group_alloc(sms, &smsq_statg_desc, 0); + if (!sms->statg) + goto err_free; + + sms->ctrg = rate_ctr_group_alloc(sms, &smsq_ctrg_desc, 0); + if (!sms->ctrg) + goto err_statg; network->sms_queue = sms; INIT_LLIST_HEAD(&sms->pending_sms); - sms->max_fail = 1; sms->network = network; - sms->max_pending = max_pending; osmo_timer_setup(&sms->push_queue, sms_submit_pending, sms); osmo_timer_setup(&sms->resend_pending, sms_resend_pending, sms); + osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network); + osmo_signal_register_handler(SS_SMS, sms_sms_cb, network); + + if (db_init(sms, sms->cfg->db_file_path, true)) { + LOGP(DMSC, LOGL_FATAL, "DB: Failed to init database: %s\n", + osmo_quote_str(sms->cfg->db_file_path, -1)); + return -1; + } + + if (db_prepare()) { + LOGP(DMSC, LOGL_FATAL, "DB: Failed to prepare database.\n"); + return -1; + } + sms_submit_pending(sms); return 0; + +err_statg: + osmo_stat_item_group_free(sms->statg); +err_free: + talloc_free(sms); + + return -ENOMEM; } +/* call-back: Given subscriber is now ready for short messages. */ static int sub_ready_for_sm(struct gsm_network *net, struct vlr_subscr *vsub) { struct gsm_sms *sms; @@ -432,14 +562,15 @@ static int sub_ready_for_sm(struct gsm_network *net, struct vlr_subscr *vsub) } /* Now try to deliver any pending SMS to this sub */ - sms = db_sms_get_unsent_for_subscr(vsub, UINT_MAX); + sms = db_sms_get_unsent_for_subscr(vsub, INT_MAX); if (!sms) return -1; - gsm411_send_sms(net, vsub, sms); + _gsm411_send_sms(net, vsub, sms); return 0; } +/* call-back for SS_SUBSCR signals */ static int sms_subscr_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { @@ -452,10 +583,12 @@ static int sms_subscr_cb(unsigned int subsys, unsigned int signal, return sub_ready_for_sm(handler_data, vsub); } +/* call-back for SS_SMS signals */ static int sms_sms_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { struct gsm_network *network = handler_data; + struct gsm_sms_queue *smq = network->sms_queue; struct sms_signal_data *sig_sms = signal_data; struct gsm_sms_pending *pending; struct vlr_subscr *vsub; @@ -463,7 +596,7 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, /* We got a new SMS and maybe should launch the queue again. */ if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) { /* TODO: For SMMA we might want to re-use the radio connection. */ - sms_queue_trigger(network->sms_queue); + sms_queue_trigger(smq); return 0; } @@ -476,26 +609,27 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, * sms that are not in our control as we just have a channel * open anyway. */ - pending = sms_find_pending(network->sms_queue, sig_sms->sms->id); + pending = sms_find_pending(smq, sig_sms->sms->id); if (!pending) return 0; switch (signal) { case S_SMS_DELIVERED: + smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_ACK); /* Remember the subscriber and clear the pending entry */ - network->sms_queue->pending -= 1; vsub = pending->vsub; vlr_subscr_get(vsub, __func__); - db_sms_delete_sent_message_by_id(pending->sms_id); - sms_pending_free(pending); + if (smq->cfg->delete_delivered) + db_sms_delete_sent_message_by_id(pending->sms_id); + sms_pending_free(smq, pending); /* Attempt to send another SMS to this subscriber */ sms_send_next(vsub); vlr_subscr_put(vsub, __func__); break; case S_SMS_MEM_EXCEEDED: - network->sms_queue->pending -= 1; - sms_pending_free(pending); - sms_queue_trigger(network->sms_queue); + smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_NOMEM); + sms_pending_free(smq, pending); + sms_queue_trigger(smq); break; case S_SMS_UNKNOWN_ERROR: /* @@ -509,10 +643,12 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, * should flag the SMS as bad. */ if (sig_sms->paging_result) { + smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_ERR); /* BAD SMS? */ db_sms_inc_deliver_attempts(sig_sms->sms); sms_pending_failed(pending, 0); } else { + smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_TIMEOUT); sms_pending_failed(pending, 1); } break; @@ -522,7 +658,8 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, } /* While here, attempt to remove an expired SMS from the DB. */ - db_sms_delete_oldest_expired_message(); + if (smq->cfg->delete_expired) + db_sms_delete_oldest_expired_message(); return 0; } @@ -533,7 +670,7 @@ int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty) struct gsm_sms_pending *pending; vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s", - smsq->max_pending, smsq->pending, VTY_NEWLINE); + smsq->cfg->max_pending, smsq->pending, VTY_NEWLINE); llist_for_each_entry(pending, &smsq->pending_sms, entry) vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s", @@ -542,22 +679,6 @@ int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty) return 0; } -int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending) -{ - LOGP(DLSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n", - smsq->max_pending, max_pending); - smsq->max_pending = max_pending; - return 0; -} - -int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail) -{ - LOGP(DLSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n", - smsq->max_fail, max_fail); - smsq->max_fail = max_fail; - return 0; -} - int sms_queue_clear(struct gsm_sms_queue *smsq) { struct gsm_sms_pending *pending, *tmp; @@ -565,9 +686,8 @@ int sms_queue_clear(struct gsm_sms_queue *smsq) llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) { LOGP(DLSMS, LOGL_NOTICE, "SMSqueue clearing for sub %llu\n", pending->vsub->id); - sms_pending_free(pending); + sms_pending_free(smsq, pending); } - smsq->pending = 0; return 0; } diff --git a/src/libmsc/smsc_vty.c b/src/libmsc/smsc_vty.c new file mode 100644 index 000000000..f30907f4b --- /dev/null +++ b/src/libmsc/smsc_vty.c @@ -0,0 +1,214 @@ +/* SMSC interface to VTY */ +/* (C) 2016-2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c) + * (C) 2009-2022 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2011 by Holger Hans Peter Freyther + * All Rights Reserved + * + * 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 "config.h" + +#include <osmocom/vty/command.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/msc/vty.h> +#include <osmocom/msc/gsm_data.h> +#include <osmocom/msc/sms_queue.h> + + +static struct gsm_network *gsmnet; +static struct sms_queue_config *smqcfg; + +/*********************************************************************** + * SMSC Config Node + ***********************************************************************/ + +static struct cmd_node smsc_node = { + SMSC_NODE, + "%s(config-smsc)# ", + 1, +}; + +DEFUN(cfg_smsc, cfg_smsc_cmd, + "smsc", "Configure SMSC options") +{ + vty->node = SMSC_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_sms_database, cfg_sms_database_cmd, + "database PATH", + "Set the path to the MSC-SMS database file\n" + "Relative or absolute file system path to the database file (default is '" SMS_DEFAULT_DB_FILE_PATH "')\n") +{ + osmo_talloc_replace_string(smqcfg, &smqcfg->db_file_path, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_sms_queue_max, cfg_sms_queue_max_cmd, + "queue max-pending <1-500>", + "SMS Queue\n" "SMS to deliver in parallel\n" "Amount\n") +{ + smqcfg->max_pending = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_sms_queue_fail, cfg_sms_queue_fail_cmd, + "queue max-failure <1-500>", + "SMS Queue\n" "Maximum number of delivery failures before giving up\n" "Amount\n") +{ + smqcfg->max_fail = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define DB_STR "SMS Database Configuration\n" + +DEFUN(cfg_sms_db_del_delivered, cfg_sms_db_del_delivered_cmd, + "database delete-delivered (0|1)", + DB_STR "Configure if delivered SMS are deleted from DB\n" + "Do not delete SMS after delivery\n" + "Delete SMS after delivery\n") +{ + smqcfg->delete_delivered = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_sms_db_del_expired, cfg_sms_db_del_expired_cmd, + "database delete-expired (0|1)", + DB_STR "Configure if expired SMS are deleted from DB\n" + "Do not delete SMS after expiration of validity period\n" + "Delete SMS after expiration of validity period\n") +{ + smqcfg->delete_expired = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_sms_def_val_per, cfg_sms_def_val_per_cmd, + "validity-period (minimum|default) <1-5256000>", + "Configure validity period for SMS\n" + "Minimum SMS validity period in minutes\n" + "Default SMS validity period in minutes\n" + "Validity period in minutes\n") +{ + if (!strcmp(argv[0], "minimum")) + smqcfg->minimum_validity_mins = atoi(argv[1]); + else + smqcfg->default_validity_mins = atoi(argv[1]); + return CMD_SUCCESS; +} + + +/*********************************************************************** + * View / Enable Node + ***********************************************************************/ + +DEFUN(show_smsqueue, + show_smsqueue_cmd, + "show sms-queue", + SHOW_STR "Display SMSqueue statistics\n") +{ + sms_queue_stats(gsmnet->sms_queue, vty); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_trigger, + smsqueue_trigger_cmd, + "sms-queue trigger", + "SMS Queue\n" "Trigger sending messages\n") +{ + sms_queue_trigger(gsmnet->sms_queue); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_max, + smsqueue_max_cmd, + "sms-queue max-pending <1-500>", + "SMS Queue\n" "SMS to deliver in parallel\n" "Amount\n") +{ + int max_pending = atoi(argv[0]); + vty_out(vty, "%% SMSqueue old max: %d new: %d%s", + smqcfg->max_pending, max_pending, VTY_NEWLINE); + smqcfg->max_pending = max_pending; + return CMD_SUCCESS; +} + +DEFUN(smsqueue_clear, + smsqueue_clear_cmd, + "sms-queue clear", + "SMS Queue\n" "Clear the queue of pending SMS\n") +{ + sms_queue_clear(gsmnet->sms_queue); + return CMD_SUCCESS; +} + +DEFUN(smsqueue_fail, + smsqueue_fail_cmd, + "sms-queue max-failure <1-500>", + "SMS Queue\n" "Maximum amount of delivery failures\n" "Amount\n") +{ + int max_fail = atoi(argv[0]); + vty_out(vty, "%% SMSqueue max failure old: %d new: %d%s", + smqcfg->max_fail, max_fail, VTY_NEWLINE); + smqcfg->max_fail = max_fail; + return CMD_SUCCESS; +} + +static int config_write_smsc(struct vty *vty) +{ + vty_out(vty, "smsc%s", VTY_NEWLINE); + + if (smqcfg->db_file_path && strcmp(smqcfg->db_file_path, SMS_DEFAULT_DB_FILE_PATH)) + vty_out(vty, " database %s%s", smqcfg->db_file_path, VTY_NEWLINE); + + vty_out(vty, " queue max-pending %u%s", smqcfg->max_pending, VTY_NEWLINE); + vty_out(vty, " queue max-failure %u%s", smqcfg->max_fail, VTY_NEWLINE); + + vty_out(vty, " database delete-delivered %u%s", smqcfg->delete_delivered, VTY_NEWLINE); + vty_out(vty, " database delete-expired %u%s", smqcfg->delete_expired, VTY_NEWLINE); + + vty_out(vty, " validity-period minimum %u%s", smqcfg->minimum_validity_mins, VTY_NEWLINE); + vty_out(vty, " validity-period default %u%s", smqcfg->default_validity_mins, VTY_NEWLINE); + + return 0; +} + +void smsc_vty_init(struct gsm_network *msc_network) +{ + OSMO_ASSERT(gsmnet == NULL); + gsmnet = msc_network; + smqcfg = msc_network->sms_queue_cfg; + + /* config node */ + install_element(CONFIG_NODE, &cfg_smsc_cmd); + install_node(&smsc_node, config_write_smsc); + install_element(SMSC_NODE, &cfg_sms_database_cmd); + install_element(SMSC_NODE, &cfg_sms_queue_max_cmd); + install_element(SMSC_NODE, &cfg_sms_queue_fail_cmd); + install_element(SMSC_NODE, &cfg_sms_db_del_delivered_cmd); + install_element(SMSC_NODE, &cfg_sms_db_del_expired_cmd); + install_element(SMSC_NODE, &cfg_sms_def_val_per_cmd); + + /* enable node */ + install_element(ENABLE_NODE, &smsqueue_trigger_cmd); + install_element(ENABLE_NODE, &smsqueue_max_cmd); + install_element(ENABLE_NODE, &smsqueue_clear_cmd); + install_element(ENABLE_NODE, &smsqueue_fail_cmd); + + /* view / enable node */ + install_element_ve(&show_smsqueue_cmd); +} diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c index 11cde934b..7ae4c7d92 100644 --- a/src/libmsc/transaction.c +++ b/src/libmsc/transaction.c @@ -29,6 +29,7 @@ #include <osmocom/msc/msub.h> #include <osmocom/msc/paging.h> #include <osmocom/msc/silent_call.h> +#include <osmocom/msc/msc_vgcs.h> void *tall_trans_ctx; @@ -73,16 +74,17 @@ struct gsm_trans *trans_find_by_id(const struct msc_a *msc_a, /*! Find a transaction by call reference * \param[in] net Network in which we should search + * \param[in] type Transaction type (e.g. TRANS_CC) * \param[in] callref Call Reference of transaction * \returns Matching transaction, if any */ -struct gsm_trans *trans_find_by_callref(const struct gsm_network *net, +struct gsm_trans *trans_find_by_callref(const struct gsm_network *net, enum trans_type type, uint32_t callref) { struct gsm_trans *trans; llist_for_each_entry(trans, &net->trans_list, entry) { - if (trans->callref == callref) + if (trans->callref == callref && trans->type == type) return trans; } return NULL; @@ -110,13 +112,81 @@ struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net, return NULL; } +struct osmo_lcls *trans_lcls_compose(const struct gsm_trans *trans, bool use_lac) +{ + if (!trans) { + LOGP(DCC, LOGL_ERROR, "LCLS: unable to fill parameters for unallocated transaction\n"); + return NULL; + } + + if (!trans->net->a.sri->sccp) + return NULL; + + struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(trans->net->a.sri->sccp); + struct osmo_lcls *lcls; + uint8_t w = osmo_ss7_pc_width(&ss7->cfg.pc_fmt); + + if (!trans->net->lcls_permitted) { + LOGP(DCC, LOGL_NOTICE, "LCLS disabled globally\n"); + return NULL; + } + + if (!trans->msc_a) { + LOGP(DCC, LOGL_ERROR, "LCLS: unable to fill parameters for transaction without connection\n"); + return NULL; + } + + if (trans->msc_a->c.ran->type != OSMO_RAT_GERAN_A) { + LOGP(DCC, LOGL_ERROR, "LCLS: only A interface is supported at the moment\n"); + return NULL; + } + + lcls = talloc_zero(trans, struct osmo_lcls); + if (!lcls) { + LOGP(DCC, LOGL_ERROR, "LCLS: failed to allocate osmo_lcls\n"); + return NULL; + } + + LOGP(DCC, LOGL_INFO, "LCLS: using %u bits (%u bytes) for node ID\n", w, w / 8); + + lcls->gcr.net_len = 3; + lcls->gcr.node = ss7->cfg.primary_pc; + + /* net id from Q.1902.3 3-5 bytes, this function gives 3 bytes exactly */ + osmo_plmn_to_bcd(lcls->gcr.net, &trans->msc_a->via_cell.lai.plmn); + + + /* TS 29.205 Table B.2.1.9.2 Call Reference ID + * 3 octets Call ID + 2 octets BSS ID + */ + lcls->gcr.cr[2] = (trans->callref >> 0) & 0xff; + lcls->gcr.cr[1] = (trans->callref >> 8) & 0xff; + lcls->gcr.cr[0] = (trans->callref >> 16) & 0xff; + osmo_store16be(use_lac ? trans->msc_a->via_cell.lai.lac : trans->msc_a->via_cell.cell_identity, &lcls->gcr.cr[3]); + + LOGP(DCC, LOGL_INFO, "LCLS: allocated %s-based CR-ID %sfor callref 0x%04x\n", use_lac ? "LAC" : "CI", + osmo_hexdump(lcls->gcr.cr, 5), trans->callref); + + lcls->config = GSM0808_LCLS_CFG_BOTH_WAY; + lcls->control = GSM0808_LCLS_CSC_CONNECT; + lcls->corr_needed = true; + lcls->gcr_available = true; + + LOGP(DCC, LOGL_DEBUG, "Filled %s\n", osmo_lcls_dump(lcls)); + LOGP(DCC, LOGL_DEBUG, "Filled %s\n", osmo_gcr_dump(lcls)); + + return lcls; +} + static const char *trans_vsub_use(enum trans_type type) { return get_value_string_or_null(trans_type_names, type) ? : "trans-type-unknown"; } +static uint32_t new_call_id = 1; + /*! Allocate a new transaction and add it to network list - * \param[in] net Netwokr in which we allocate transaction + * \param[in] net Network in which we allocate transaction * \param[in] subscr Subscriber for which we allocate transaction * \param[in] protocol Protocol (CC/SMS/...) * \param[in] callref Call Reference @@ -130,8 +200,8 @@ struct gsm_trans *trans_alloc(struct gsm_network *net, int subsys = trans_log_subsys(type); struct gsm_trans *trans; - /* a valid subscriber is indispensable */ - if (vsub == NULL) { + /* A valid subscriber is indispensable, except for voice group/broadcast calls. */ + if (vsub == NULL && type != TRANS_GCC && type != TRANS_BCC) { LOGP(subsys, LOGL_ERROR, "unable to alloc transaction, invalid subscriber (NULL)\n"); return NULL; } @@ -146,13 +216,15 @@ struct gsm_trans *trans_alloc(struct gsm_network *net, .log_subsys = subsys, .transaction_id = trans_id, .callref = callref, + .call_id = new_call_id++, .net = net, /* empty bearer_cap: make sure the speech_ver array is empty */ .bearer_cap = { .speech_ver = { -1 }, }, }; - vlr_subscr_get(vsub, trans_vsub_use(type)); + if (vsub) + vlr_subscr_get(vsub, trans_vsub_use(type)); llist_add_tail(&trans->entry, &net->trans_list); LOG_TRANS(trans, LOGL_DEBUG, "New transaction\n"); @@ -170,6 +242,14 @@ void trans_free(struct gsm_trans *trans) LOG_TRANS(trans, LOGL_DEBUG, "Freeing transaction\n"); switch (trans->type) { + case TRANS_GCC: + gsm44068_bcc_gcc_trans_free(trans); + usage_token = MSC_A_USE_GCC; + break; + case TRANS_BCC: + gsm44068_bcc_gcc_trans_free(trans); + usage_token = MSC_A_USE_BCC; + break; case TRANS_CC: _gsm48_cc_trans_free(trans); usage_token = MSC_A_USE_CC; @@ -281,6 +361,8 @@ void trans_conn_closed(const struct msc_a *msc_a) } const struct value_string trans_type_names[] = { + { TRANS_GCC, "GCC" }, + { TRANS_BCC, "BCC" }, { TRANS_CC, "CC" }, { TRANS_SMS, "SMS" }, { TRANS_USSD, "NCSS" }, @@ -291,6 +373,10 @@ const struct value_string trans_type_names[] = { uint8_t trans_type_to_gsm48_proto(enum trans_type type) { switch (type) { + case TRANS_GCC: + return GSM48_PDISC_GROUP_CC; + case TRANS_BCC: + return GSM48_PDISC_BCAST_CC; case TRANS_CC: case TRANS_SILENT_CALL: return GSM48_PDISC_CC; @@ -303,3 +389,25 @@ uint8_t trans_type_to_gsm48_proto(enum trans_type type) } } + +const char *trans_name(const struct gsm_trans *trans) +{ + static char namebuf[32]; + if (!trans) + return "NULL"; + switch (trans->type) { + case TRANS_CC: + snprintf(namebuf, sizeof(namebuf), "%s:%s", + trans_type_name(trans->type), gsm48_cc_state_name(trans->cc.state)); + return namebuf; + + case TRANS_GCC: + case TRANS_BCC: + snprintf(namebuf, sizeof(namebuf), "%s:%s", + trans_type_name(trans->type), gsm44068_group_id_string(trans->callref)); + return namebuf; + + default: + return trans_type_name(trans->type); + } +} diff --git a/src/libmsc/transaction_cc.c b/src/libmsc/transaction_cc.c new file mode 100644 index 000000000..2a540bfa5 --- /dev/null +++ b/src/libmsc/transaction_cc.c @@ -0,0 +1,120 @@ +/* Filter/overlay codec and CSD bearer service selections for voice calls/CSD, + * across MS, RAN and CN limitations + * + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Oliver Smith + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * 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 <osmocom/msc/transaction_cc.h> +#include <osmocom/msc/codec_filter.h> +#include <osmocom/msc/csd_filter.h> + +void trans_cc_filter_init(struct gsm_trans *trans) +{ + trans->cc.codecs = (struct codec_filter){}; + trans->cc.csd = (struct csd_filter){}; +} + +void trans_cc_filter_set_ran(struct gsm_trans *trans, enum osmo_rat_type ran_type) +{ + codec_filter_set_ran(&trans->cc.codecs, ran_type); + csd_filter_set_ran(&trans->cc.csd, ran_type); +} + +void trans_cc_filter_set_bss(struct gsm_trans *trans, struct msc_a *msc_a) +{ + codec_filter_set_bss(&trans->cc.codecs, &msc_a->cc.compl_l3_codec_list_bss_supported); + + /* For CSD, there is no list of supported bearer services passed in + * Complete Layer 3. TODO: make it configurable? */ +} + +void _trans_cc_filter_run(const char *file, int line, struct gsm_trans *trans) +{ + switch (trans->bearer_cap.transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + codec_filter_run(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote); + LOG_TRANS_CAT_SRC(trans, DCC, LOGL_DEBUG, file, line, "codecs: %s\n", + codec_filter_to_str(&trans->cc.codecs, &trans->cc.local, &trans->cc.remote)); + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + csd_filter_run(&trans->cc.csd, &trans->cc.local, &trans->cc.remote); + LOG_TRANS_CAT_SRC(trans, DCC, LOGL_DEBUG, file, line, "codec/BS: %s\n", + csd_filter_to_str(&trans->cc.csd, &trans->cc.local, &trans->cc.remote)); + break; + default: + LOG_TRANS_CAT_SRC(trans, DCC, LOGL_ERROR, file, line, + "Handling of information transfer capability %d not implemented\n", + trans->bearer_cap.transfer); + break; + } +} + +void trans_cc_filter_set_ms_from_bc(struct gsm_trans *trans, const struct gsm_mncc_bearer_cap *bcap) +{ + trans->cc.codecs.ms = (struct sdp_audio_codecs){0}; + trans->cc.csd.ms = (struct csd_bs_list){0}; + + if (!bcap) + return; + + switch (bcap->transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + sdp_audio_codecs_from_bearer_cap(&trans->cc.codecs.ms, bcap); + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + sdp_audio_codecs_set_csd(&trans->cc.codecs.ms); + csd_bs_list_from_bearer_cap(&trans->cc.csd.ms, bcap); + break; + default: + LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n", + bcap->transfer); + break; + } +} + +void trans_cc_set_remote_from_bc(struct gsm_trans *trans, const struct gsm_mncc_bearer_cap *bcap) +{ + trans->cc.remote.audio_codecs = (struct sdp_audio_codecs){0}; + trans->cc.remote.bearer_services = (struct csd_bs_list){0}; + + if (!bcap) + return; + + switch (bcap->transfer) { + case GSM48_BCAP_ITCAP_SPEECH: + sdp_audio_codecs_from_bearer_cap(&trans->cc.remote.audio_codecs, bcap); + break; + case GSM48_BCAP_ITCAP_3k1_AUDIO: + case GSM48_BCAP_ITCAP_FAX_G3: + case GSM48_BCAP_ITCAP_UNR_DIG_INF: + sdp_audio_codecs_set_csd(&trans->cc.remote.audio_codecs); + csd_bs_list_from_bearer_cap(&trans->cc.remote.bearer_services, bcap); + break; + default: + LOG_TRANS(trans, LOGL_ERROR, "Handling of information transfer capability %d not implemented\n", + bcap->transfer); + break; + } +} diff --git a/src/libsmpputil/Makefile.am b/src/libsmpputil/Makefile.am new file mode 100644 index 000000000..b180ddf6f --- /dev/null +++ b/src/libsmpputil/Makefile.am @@ -0,0 +1,25 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir) +AM_CFLAGS= \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOSCCP_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ + $(LIBOSMOGSUPCLIENT_CFLAGS) \ + $(LIBSMPP34_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(NULL) + +noinst_HEADERS = \ + $(NULL) + +noinst_LIBRARIES = libsmpputil.a + +libsmpputil_a_SOURCES = \ + smpp_utils.c \ + smpp_vty.c \ + smpp_msc.c \ + smpp_smsc.c \ + $(NULL) diff --git a/src/libmsc/smpp_openbsc.c b/src/libsmpputil/smpp_msc.c index e4c3891c2..0c2a9282f 100644 --- a/src/libmsc/smpp_openbsc.c +++ b/src/libsmpputil/smpp_msc.c @@ -1,6 +1,6 @@ /* OpenBSC SMPP 3.4 interface, SMSC-side implementation */ -/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org> +/* (C) 2012-2022 by Harald Welte <laforge@gnumonks.org> * * All Rights Reserved * @@ -24,6 +24,7 @@ #include <string.h> #include <stdint.h> #include <errno.h> +#include <time.h> #include <smpp34.h> #include <smpp34_structs.h> @@ -47,8 +48,7 @@ #include <osmocom/msc/gsm_subscriber.h> #include <osmocom/msc/vlr.h> #include <osmocom/msc/msc_a.h> - -#include "smpp_smsc.h" +#include <osmocom/smpp/smpp_smsc.h> #define VSUB_USE_SMPP "SMPP" #define VSUB_USE_SMPP_CMD "SMPP-cmd" @@ -122,6 +122,8 @@ static int smpp34_submit_tlv_msg_payload(const struct tlv_t *t, static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net, const struct submit_sm_t *submit) { + time_t t_now = time(NULL); + time_t t_validity_absolute; const uint8_t *sms_msg = NULL; unsigned int sms_msg_len = 0; struct vlr_subscr *dest; @@ -239,10 +241,16 @@ static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net, } if (mode == MODE_7BIT) { - uint8_t ud_len = 0, padbits = 0; + unsigned int ud_len = 0, padbits = 0; sms->data_coding_scheme = GSM338_DCS_1111_7BIT; if (sms->ud_hdr_ind) { ud_len = *sms_msg + 1; + if (ud_len > sms_msg_len) { + sms_free(sms); + LOGP(DLSMS, LOGL_ERROR, "invalid ud_len=%u > sms_msg_len=%u\n", ud_len, + sms_msg_len); + return ESME_RINVPARLEN; + } printf("copying %u bytes user data...\n", ud_len); memcpy(sms->user_data, sms_msg, OSMO_MIN(ud_len, sizeof(sms->user_data))); @@ -250,8 +258,7 @@ static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net, sms_msg_len -= ud_len; padbits = 7 - (ud_len % 7); } - gsm_septets2octets(sms->user_data+ud_len, sms_msg, - sms_msg_len, padbits); + gsm_septet_pack(sms->user_data+ud_len, sms_msg, sms_msg_len, padbits); sms->user_data_len = (ud_len*8 + padbits)/7 + sms_msg_len;/* SEPTETS */ /* FIXME: sms->text */ } else { @@ -259,12 +266,25 @@ static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net, sms->user_data_len = sms_msg_len; } + t_validity_absolute = smpp_parse_time_format((const char *) submit->validity_period, &t_now); + if (!t_validity_absolute) + sms->validity_minutes = net->sms_queue_cfg->default_validity_mins; + else + sms->validity_minutes = (t_validity_absolute - t_now) / 60; + + if (sms->validity_minutes < net->sms_queue_cfg->minimum_validity_mins) { + LOGP(DLSMS, LOGL_INFO, "SMS to %s: Overriding ESME-provided validity period (%lu) " + "with minimum SMSC validity period (%u) minutes\n", submit->destination_addr, + sms->validity_minutes, net->sms_queue_cfg->minimum_validity_mins); + sms->validity_minutes = net->sms_queue_cfg->minimum_validity_mins; + } + *psms = sms; return ESME_ROK; } /*! \brief handle incoming libsmpp34 ssubmit_sm_t from remote ESME */ -int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit, +int handle_smpp_submit(struct smpp_esme *esme, struct submit_sm_t *submit, struct submit_sm_resp_t *submit_r) { struct gsm_sms *sms; @@ -315,11 +335,11 @@ int handle_smpp_submit(struct osmo_esme *esme, struct submit_sm_t *submit, static void alert_all_esme(struct smsc *smsc, struct vlr_subscr *vsub, uint8_t smpp_avail_status) { - struct osmo_esme *esme; + struct smpp_esme *esme; llist_for_each_entry(esme, &smsc->esme_list, list) { /* we currently send an alert notification to each ESME that is - * connected, and do not require a (non-existant) delivery + * connected, and do not require a (non-existent) delivery * pending flag to be set before. */ if (!esme->bind_flags) { LOGP(DSMPP, LOGL_DEBUG, @@ -327,9 +347,7 @@ static void alert_all_esme(struct smsc *smsc, struct vlr_subscr *vsub, continue; } if (esme->acl && !esme->acl->alert_notifications) { - LOGP(DSMPP, LOGL_DEBUG, - "[%s] is not set to receive Alert Notifications\n", - esme->system_id); + LOGPESME(esme->esme, LOGL_DEBUG, "is not set to receive Alert Notifications\n"); continue; } if (esme->acl && esme->acl->deliver_src_imsi) { @@ -367,7 +385,7 @@ static int smpp_sms_cb(unsigned int subsys, unsigned int signal, * to the ESME */ case S_SMS_UNKNOWN_ERROR: if (sms->smpp.transaction_mode) { - /* Send back the SUBMIT-SM response with apropriate error */ + /* Send back the SUBMIT-SM response with appropriate error */ LOGP(DLSMS, LOGL_INFO, "SMPP SUBMIT-SM: Error\n"); rc = smpp_tx_submit_r(sms->smpp.esme, sms->smpp.sequence_nr, @@ -573,7 +591,7 @@ static void smpp_cmd_free(struct osmo_smpp_cmd *cmd) talloc_free(cmd); } -void smpp_cmd_flush_pending(struct osmo_esme *esme) +void smpp_cmd_flush_pending(struct smpp_esme *esme) { struct osmo_smpp_cmd *cmd, *next; @@ -641,7 +659,7 @@ static void smpp_deliver_sm_cb(void *data) smpp_cmd_err(data, ESME_RSYSERR); } -static int smpp_cmd_enqueue(struct osmo_esme *esme, +static int smpp_cmd_enqueue(struct smpp_esme *esme, struct vlr_subscr *vsub, struct gsm_sms *sms, uint32_t sequence_number) { @@ -670,7 +688,7 @@ static int smpp_cmd_enqueue(struct osmo_esme *esme, return 0; } -struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, +struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct smpp_esme *esme, uint32_t sequence_nr) { struct osmo_smpp_cmd *cmd; @@ -682,7 +700,7 @@ struct osmo_smpp_cmd *smpp_cmd_find_by_seqnum(struct osmo_esme *esme, return NULL; } -static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms, +static int deliver_to_esme(struct smpp_esme *esme, struct gsm_sms *sms, struct msc_a *msc_a) { struct deliver_sm_t deliver; @@ -770,15 +788,18 @@ static int deliver_to_esme(struct osmo_esme *esme, struct gsm_sms *sms, sms->msg_ref); ret = smpp_tx_deliver(esme, &deliver); + destroy_tlv(deliver.tlv); if (ret < 0) return ret; + OSMO_ASSERT(!sms->smpp.esme); + smpp_esme_get(esme); + sms->smpp.esme = esme; + return smpp_cmd_enqueue(esme, vsub, sms, deliver.sequence_number); } -static struct smsc *g_smsc; - bool smpp_route_smpp_first() { return (bool)(g_smsc->smpp_first); @@ -786,7 +807,7 @@ bool smpp_route_smpp_first() int smpp_try_deliver(struct gsm_sms *sms, struct msc_a *msc_a) { - struct osmo_esme *esme; + struct smpp_esme *esme; struct osmo_smpp_addr dst; int rc; @@ -808,8 +829,8 @@ struct smsc *smsc_from_vty(struct vty *v) return g_smsc; } -/*! \brief Allocate the OpenBSC SMPP interface struct and init VTY. */ -int smpp_openbsc_alloc_init(void *ctx) +/*! \brief Allocate the OsmoMSC SMPP interface struct and init VTY. */ +int smpp_msc_alloc_init(void *ctx) { g_smsc = smpp_smsc_alloc_init(ctx); if (!g_smsc) { @@ -820,9 +841,9 @@ int smpp_openbsc_alloc_init(void *ctx) return smpp_vty_init(); } -/*! \brief Launch the OpenBSC SMPP interface with the parameters set from VTY. +/*! \brief Launch the OsmoMSC SMPP interface with the parameters set from VTY. */ -int smpp_openbsc_start(struct gsm_network *net) +int smpp_msc_start(struct gsm_network *net) { int rc; g_smsc->priv = net; @@ -843,4 +864,3 @@ int smpp_openbsc_start(struct gsm_network *net) return 0; } - diff --git a/src/libmsc/smpp_smsc.c b/src/libsmpputil/smpp_smsc.c index 3bfb81a3d..34e24c513 100644 --- a/src/libmsc/smpp_smsc.c +++ b/src/libsmpputil/smpp_smsc.c @@ -38,16 +38,11 @@ #include <osmocom/core/logging.h> #include <osmocom/core/write_queue.h> #include <osmocom/core/talloc.h> - -#include "smpp_smsc.h" +#include <osmocom/gsm/protocol/gsm_04_11.h> #include <osmocom/msc/debug.h> #include <osmocom/msc/gsm_data.h> - -/*! \brief Ugly wrapper. libsmpp34 should do this itself! */ -#define SMPP34_UNPACK(rc, type, str, data, len) \ - memset(str, 0, sizeof(*str)); \ - rc = smpp34_unpack(type, str, data, len) +#include <osmocom/smpp/smpp_smsc.h> enum emse_bind { ESME_BIND_RX = 0x01, @@ -164,12 +159,12 @@ void smpp_acl_delete(struct osmo_smpp_acl *acl) /* kill any active ESMEs */ if (acl->esme) { - struct osmo_esme *esme = acl->esme; + struct esme *esme = acl->esme->esme; osmo_fd_unregister(&esme->wqueue.bfd); close(esme->wqueue.bfd.fd); esme->wqueue.bfd.fd = -1; - esme->acl = NULL; - smpp_esme_put(esme); + smpp_esme_put(acl->esme); + acl->esme = NULL; } /* delete all routes for this ACL */ @@ -236,17 +231,17 @@ int smpp_route_pfx_del(struct osmo_smpp_acl *acl, /*! \brief increaes the use/reference count */ -void smpp_esme_get(struct osmo_esme *esme) +void smpp_esme_get(struct smpp_esme *esme) { esme->use++; } -static void esme_destroy(struct osmo_esme *esme) +static void esme_destroy(struct smpp_esme *esme) { - osmo_wqueue_clear(&esme->wqueue); - if (esme->wqueue.bfd.fd >= 0) { - osmo_fd_unregister(&esme->wqueue.bfd); - close(esme->wqueue.bfd.fd); + osmo_wqueue_clear(&esme->esme->wqueue); + if (esme->esme->wqueue.bfd.fd >= 0) { + osmo_fd_unregister(&esme->esme->wqueue.bfd); + close(esme->esme->wqueue.bfd.fd); } smpp_cmd_flush_pending(esme); llist_del(&esme->list); @@ -255,7 +250,7 @@ static void esme_destroy(struct osmo_esme *esme) talloc_free(esme); } -static uint32_t esme_inc_seq_nr(struct osmo_esme *esme) +uint32_t esme_inc_seq_nr(struct esme *esme) { esme->own_seq_nr++; if (esme->own_seq_nr > 0x7fffffff) @@ -265,7 +260,7 @@ static uint32_t esme_inc_seq_nr(struct osmo_esme *esme) } /*! \brief decrease the use/reference count, free if it is 0 */ -void smpp_esme_put(struct osmo_esme *esme) +void smpp_esme_put(struct smpp_esme *esme) { esme->use--; if (esme->use <= 0) @@ -273,7 +268,7 @@ void smpp_esme_put(struct osmo_esme *esme) } /*! \brief try to find a SMPP route (ESME) for given destination */ -int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct osmo_esme **pesme) +int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struct smpp_esme **pesme) { struct osmo_smpp_route *r; struct osmo_smpp_acl *acl = NULL; @@ -313,56 +308,48 @@ int smpp_route(const struct smsc *smsc, const struct osmo_smpp_addr *dest, struc } if (acl && acl->esme) { - struct osmo_esme *esme; + struct smpp_esme *esme; DEBUGP(DSMPP, "ACL even has ESME, we can route to it!\n"); esme = acl->esme; if (esme->bind_flags & ESME_BIND_RX) { *pesme = esme; return 0; } else - LOGP(DSMPP, LOGL_NOTICE, "[%s] is matching route, " - "but not bound for Rx, discarding MO SMS\n", - esme->system_id); + LOGPESME(esme->esme, LOGL_NOTICE, "is matching route, but not bound for Rx, discarding MO SMS\n"); } *pesme = NULL; if (acl) - return GSM48_CC_CAUSE_NETWORK_OOO; + return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER; else - return GSM48_CC_CAUSE_UNASSIGNED_NR; -} - - -/*! \brief initialize the libsmpp34 data structure for a response */ -#define INIT_RESP(type, resp, req) { \ - memset((resp), 0, sizeof(*(resp))); \ - (resp)->command_length = 0; \ - (resp)->command_id = type; \ - (resp)->command_status = ESME_ROK; \ - (resp)->sequence_number = (req)->sequence_number; \ + return GSM411_RP_CAUSE_MO_NUM_UNASSIGNED; } /*! \brief pack a libsmpp34 data strcutrure and send it to the ESME */ -#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr) -static int pack_and_send(struct osmo_esme *esme, uint32_t type, void *ptr) +int pack_and_send(struct esme *esme, uint32_t type, void *ptr) { - struct msgb *msg = msgb_alloc(4096, "SMPP_Tx"); + struct msgb *msg; int rc, rlen; + + /* the socket was closed. Avoid allocating + enqueueing msgb, see + * https://osmocom.org/issues/3278 */ + if (esme->wqueue.bfd.fd == -1) + return -EIO; + + msg = msgb_alloc(4096, "SMPP_Tx"); if (!msg) return -ENOMEM; rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr); if (rc != 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme, "during smpp34_pack()\n"); msgb_free(msg); return -EINVAL; } msgb_put(msg, rlen); if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n", - esme->system_id); + LOGPESME(esme, LOGL_ERROR, "Write queue full. Dropping message\n"); msgb_free(msg); return -EAGAIN; } @@ -370,7 +357,7 @@ static int pack_and_send(struct osmo_esme *esme, uint32_t type, void *ptr) } /*! \brief transmit a generic NACK to a remote ESME */ -static int smpp_tx_gen_nack(struct osmo_esme *esme, uint32_t seq, uint32_t status) +static int smpp_tx_gen_nack(struct esme *esme, uint32_t seq, uint32_t status) { struct generic_nack_t nack; char buf[SMALL_BUFF]; @@ -380,19 +367,11 @@ static int smpp_tx_gen_nack(struct osmo_esme *esme, uint32_t seq, uint32_t statu nack.sequence_number = seq; nack.command_status = status; - LOGP(DSMPP, LOGL_ERROR, "[%s] Tx GENERIC NACK: %s\n", - esme->system_id, str_command_status(status, buf)); + LOGPESME(esme, LOGL_ERROR, "Tx GENERIC NACK: %s\n", str_command_status(status, buf)); return PACK_AND_SEND(esme, &nack); } -/*! \brief retrieve SMPP command ID from a msgb */ -static inline uint32_t smpp_msgb_cmdid(struct msgb *msg) -{ - uint8_t *tmp = msgb_data(msg) + 4; - return ntohl(*(uint32_t *)tmp); -} - /*! \brief retrieve SMPP sequence number from a msgb */ static inline uint32_t smpp_msgb_seq(struct msgb *msg) { @@ -401,7 +380,7 @@ static inline uint32_t smpp_msgb_seq(struct msgb *msg) } /*! \brief handle an incoming SMPP generic NACK */ -static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_gen_nack(struct esme *esme, struct msgb *msg) { struct generic_nack_t nack; char buf[SMALL_BUFF]; @@ -410,18 +389,16 @@ static int smpp_handle_gen_nack(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, GENERIC_NACK, &nack, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme, "in smpp34_unpack()\n"); return rc; } - LOGP(DSMPP, LOGL_ERROR, "[%s] Rx GENERIC NACK: %s\n", - esme->system_id, str_command_status(nack.command_status, buf)); + LOGPESME(esme, LOGL_ERROR, "Rx GENERIC NACK: %s\n", str_command_status(nack.command_status, buf)); return 0; } -static int _process_bind(struct osmo_esme *esme, uint8_t if_version, +static int _process_bind(struct smpp_esme *esme, uint8_t if_version, uint32_t bind_flags, const char *sys_id, const char *passwd) { @@ -434,9 +411,9 @@ static int _process_bind(struct osmo_esme *esme, uint8_t if_version, return ESME_RALYBND; esme->smpp_version = if_version; - snprintf(esme->system_id, sizeof(esme->system_id), "%s", sys_id); + snprintf(esme->esme->system_id, sizeof(esme->esme->system_id), "%s", sys_id); - acl = smpp_acl_by_system_id(esme->smsc, esme->system_id); + acl = smpp_acl_by_system_id(esme->smsc, esme->esme->system_id); if (!esme->smsc->accept_all) { if (!acl) { /* This system is unknown */ @@ -460,7 +437,7 @@ static int _process_bind(struct osmo_esme *esme, uint8_t if_version, /*! \brief handle an incoming SMPP BIND RECEIVER */ -static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_bind_rx(struct smpp_esme *esme, struct msgb *msg) { struct bind_receiver_t bind; struct bind_receiver_resp_t bind_r; @@ -469,8 +446,7 @@ static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, BIND_RECEIVER, &bind, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } @@ -483,22 +459,21 @@ static int smpp_handle_bind_rx(struct osmo_esme *esme, struct msgb *msg) (const char *)bind.system_id, (const char *)bind.password); bind_r.command_status = rc; - return PACK_AND_SEND(esme, &bind_r); + return PACK_AND_SEND(esme->esme, &bind_r); } /*! \brief handle an incoming SMPP BIND TRANSMITTER */ -static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_bind_tx(struct smpp_esme *esme, struct msgb *msg) { struct bind_transmitter_t bind; struct bind_transmitter_resp_t bind_r; - struct tlv_t tlv; + struct tlv_t tlv = {}; int rc; SMPP34_UNPACK(rc, BIND_TRANSMITTER, &bind, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } @@ -520,13 +495,13 @@ static int smpp_handle_bind_tx(struct osmo_esme *esme, struct msgb *msg) tlv.value.val16 = esme->smpp_version; build_tlv(&bind_r.tlv, &tlv); - rc = PACK_AND_SEND(esme, &bind_r); + rc = PACK_AND_SEND(esme->esme, &bind_r); destroy_tlv(bind_r.tlv); return rc; } /*! \brief handle an incoming SMPP BIND TRANSCEIVER */ -static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_bind_trx(struct smpp_esme *esme, struct msgb *msg) { struct bind_transceiver_t bind; struct bind_transceiver_resp_t bind_r; @@ -535,8 +510,7 @@ static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, BIND_TRANSCEIVER, &bind, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } @@ -549,11 +523,11 @@ static int smpp_handle_bind_trx(struct osmo_esme *esme, struct msgb *msg) (const char *)bind.system_id, (const char *)bind.password); bind_r.command_status = rc; - return PACK_AND_SEND(esme, &bind_r); + return PACK_AND_SEND(esme->esme, &bind_r); } /*! \brief handle an incoming SMPP UNBIND */ -static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_unbind(struct smpp_esme *esme, struct msgb *msg) { struct unbind_t unbind; struct unbind_resp_t unbind_r; @@ -562,14 +536,13 @@ static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, UNBIND, &unbind, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } INIT_RESP(UNBIND_RESP, &unbind_r, &unbind); - LOGP(DSMPP, LOGL_INFO, "[%s] Rx UNBIND\n", esme->system_id); + LOGPESME(esme->esme, LOGL_INFO, "Rx UNBIND\n"); if (esme->bind_flags == 0) { unbind_r.command_status = ESME_RINVBNDSTS; @@ -578,11 +551,11 @@ static int smpp_handle_unbind(struct osmo_esme *esme, struct msgb *msg) esme->bind_flags = 0; err: - return PACK_AND_SEND(esme, &unbind_r); + return PACK_AND_SEND(esme->esme, &unbind_r); } /*! \brief handle an incoming SMPP ENQUIRE LINK */ -static int smpp_handle_enq_link(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_enq_link(struct smpp_esme *esme, struct msgb *msg) { struct enquire_link_t enq; struct enquire_link_resp_t enq_r; @@ -591,22 +564,21 @@ static int smpp_handle_enq_link(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, ENQUIRE_LINK, &enq, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } - LOGP(DSMPP, LOGL_DEBUG, "[%s] Rx Enquire Link\n", esme->system_id); + LOGPESME(esme->esme, LOGL_DEBUG, "Rx Enquire Link\n"); INIT_RESP(ENQUIRE_LINK_RESP, &enq_r, &enq); - LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx Enquire Link Response\n", esme->system_id); + LOGPESME(esme->esme, LOGL_DEBUG, "Tx Enquire Link Response\n"); - return PACK_AND_SEND(esme, &enq_r); + return PACK_AND_SEND(esme->esme, &enq_r); } /*! \brief send a SUBMIT-SM RESPONSE to a remote ESME */ -int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, +int smpp_tx_submit_r(struct smpp_esme *esme, uint32_t sequence_nr, uint32_t command_status, char *msg_id) { struct submit_sm_resp_t submit_r; @@ -618,7 +590,7 @@ int smpp_tx_submit_r(struct osmo_esme *esme, uint32_t sequence_nr, submit_r.sequence_number= sequence_nr; snprintf((char *) submit_r.message_id, sizeof(submit_r.message_id), "%s", msg_id); - return PACK_AND_SEND(esme, &submit_r); + return PACK_AND_SEND(esme->esme, &submit_r); } static const struct value_string smpp_avail_strs[] = { @@ -629,7 +601,7 @@ static const struct value_string smpp_avail_strs[] = { }; /*! \brief send an ALERT_NOTIFICATION to a remote ESME */ -int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi, +int smpp_tx_alert(struct smpp_esme *esme, uint8_t ton, uint8_t npi, const char *addr, uint8_t avail_status) { struct alert_notification_t alert; @@ -640,7 +612,7 @@ int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi, alert.command_length = 0; alert.command_id = ALERT_NOTIFICATION; alert.command_status = ESME_ROK; - alert.sequence_number = esme_inc_seq_nr(esme); + alert.sequence_number = esme_inc_seq_nr(esme->esme); alert.source_addr_ton = ton; alert.source_addr_npi = npi; snprintf((char *)alert.source_addr, sizeof(alert.source_addr), "%s", addr); @@ -650,29 +622,28 @@ int smpp_tx_alert(struct osmo_esme *esme, uint8_t ton, uint8_t npi, tlv.value.val08 = avail_status; build_tlv(&alert.tlv, &tlv); - LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx ALERT_NOTIFICATION (%s/%u/%u): %s\n", - esme->system_id, alert.source_addr, alert.source_addr_ton, - alert.source_addr_npi, - get_value_string(smpp_avail_strs, avail_status)); + LOGPESME(esme->esme, LOGL_DEBUG, "Tx ALERT_NOTIFICATION (%s/%u/%u): %s\n", + alert.source_addr, alert.source_addr_ton, + alert.source_addr_npi, + get_value_string(smpp_avail_strs, avail_status)); - rc = PACK_AND_SEND(esme, &alert); + rc = PACK_AND_SEND(esme->esme, &alert); destroy_tlv(alert.tlv); return rc; } /* \brief send a DELIVER-SM message to given ESME */ -int smpp_tx_deliver(struct osmo_esme *esme, struct deliver_sm_t *deliver) +int smpp_tx_deliver(struct smpp_esme *esme, struct deliver_sm_t *deliver) { - deliver->sequence_number = esme_inc_seq_nr(esme); + deliver->sequence_number = esme_inc_seq_nr(esme->esme); - LOGP(DSMPP, LOGL_DEBUG, "[%s] Tx DELIVER-SM (from %s)\n", - esme->system_id, deliver->source_addr); + LOGPESME(esme->esme, LOGL_DEBUG, "Tx DELIVER-SM (from %s)\n", deliver->source_addr); - return PACK_AND_SEND(esme, deliver); + return PACK_AND_SEND(esme->esme, deliver); } /*! \brief handle an incoming SMPP DELIVER-SM RESPONSE */ -static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_deliver_resp(struct smpp_esme *esme, struct msgb *msg) { struct deliver_sm_resp_t deliver_r; struct osmo_smpp_cmd *cmd; @@ -682,16 +653,14 @@ static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, DELIVER_SM_RESP, &deliver_r, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } cmd = smpp_cmd_find_by_seqnum(esme, deliver_r.sequence_number); if (!cmd) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Rx DELIVER-SM RESP !? (%s)\n", - esme->system_id, get_value_string(smpp_status_strs, - deliver_r.command_status)); + LOGPESME(esme->esme, LOGL_ERROR, "Rx DELIVER-SM RESP !? (%s)\n", + get_value_string(smpp_status_strs, deliver_r.command_status)); return -1; } @@ -700,15 +669,14 @@ static int smpp_handle_deliver_resp(struct osmo_esme *esme, struct msgb *msg) else smpp_cmd_err(cmd, deliver_r.command_status); - LOGP(DSMPP, LOGL_INFO, "[%s] Rx DELIVER-SM RESP (%s)\n", - esme->system_id, get_value_string(smpp_status_strs, - deliver_r.command_status)); + LOGPESME(esme->esme, LOGL_INFO, "Rx DELIVER-SM RESP (%s)\n", + get_value_string(smpp_status_strs, deliver_r.command_status)); return 0; } /*! \brief handle an incoming SMPP SUBMIT-SM */ -static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg) +static int smpp_handle_submit(struct smpp_esme *esme, struct msgb *msg) { struct submit_sm_t submit; struct submit_sm_resp_t submit_r; @@ -718,8 +686,7 @@ static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg) SMPP34_UNPACK(rc, SUBMIT_SM, &submit, msgb_data(msg), msgb_length(msg)); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error in smpp34_unpack():%s\n", - esme->system_id, smpp34_strerror); + LOGPESMERR(esme->esme, "in smpp34_unpack()\n"); return rc; } @@ -727,34 +694,34 @@ static int smpp_handle_submit(struct osmo_esme *esme, struct msgb *msg) if (!(esme->bind_flags & ESME_BIND_TX)) { submit_r.command_status = ESME_RINVBNDSTS; - return PACK_AND_SEND(esme, &submit_r); + destroy_tlv(submit.tlv); + return PACK_AND_SEND(esme->esme, &submit_r); } - LOGP(DSMPP, LOGL_INFO, "[%s] Rx SUBMIT-SM (%s/%u/%u)\n", - esme->system_id, submit.destination_addr, - submit.dest_addr_ton, submit.dest_addr_npi); + LOGPESME(esme->esme, LOGL_INFO, "Rx SUBMIT-SM (%s/%u/%u)\n", + submit.destination_addr, submit.dest_addr_ton, submit.dest_addr_npi); INIT_RESP(SUBMIT_SM_RESP, &submit_r, &submit); rc = handle_smpp_submit(esme, &submit, &submit_r); + destroy_tlv(submit.tlv); if (rc == 0) - return PACK_AND_SEND(esme, &submit_r); + return PACK_AND_SEND(esme->esme, &submit_r); return rc; } /*! \brief one complete SMPP PDU from the ESME has been received */ -static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses) +static int smpp_pdu_rx(struct smpp_esme *esme, struct msgb *msg __uses) { uint32_t cmd_id = smpp_msgb_cmdid(msg); int rc = 0; - LOGP(DSMPP, LOGL_DEBUG, "[%s] smpp_pdu_rx(%s)\n", esme->system_id, - msgb_hexdump(msg)); + LOGPESME(esme->esme, LOGL_DEBUG, "smpp_pdu_rx(%s)\n", msgb_hexdump(msg)); switch (cmd_id) { case GENERIC_NACK: - rc = smpp_handle_gen_nack(esme, msg); + rc = smpp_handle_gen_nack(esme->esme, msg); break; case BIND_RECEIVER: rc = smpp_handle_bind_rx(esme, msg); @@ -785,13 +752,11 @@ static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses) case QUERY_SM: case REPLACE_SM: case SUBMIT_MULTI: - LOGP(DSMPP, LOGL_NOTICE, "[%s] Unimplemented PDU Command " - "0x%08x\n", esme->system_id, cmd_id); + LOGPESME(esme->esme, LOGL_NOTICE, "Unimplemented PDU Command 0x%08x\n", cmd_id); break; default: - LOGP(DSMPP, LOGL_ERROR, "[%s] Unknown PDU Command 0x%08x\n", - esme->system_id, cmd_id); - rc = smpp_tx_gen_nack(esme, smpp_msgb_seq(msg), ESME_RINVCMDID); + LOGPESME(esme->esme, LOGL_ERROR, "Unknown PDU Command 0x%08x\n", cmd_id); + rc = smpp_tx_gen_nack(esme->esme, smpp_msgb_seq(msg), ESME_RINVCMDID); break; } @@ -818,7 +783,8 @@ static int smpp_pdu_rx(struct osmo_esme *esme, struct msgb *msg __uses) /* !\brief call-back when per-ESME TCP socket has some data to be read */ static int esme_link_read_cb(struct osmo_fd *ofd) { - struct osmo_esme *esme = ofd->data; + struct smpp_esme *e = ofd->data; + struct esme *esme = e->esme; uint32_t len; uint8_t *lenptr = (uint8_t *) &len; uint8_t *cur; @@ -830,8 +796,7 @@ static int esme_link_read_cb(struct osmo_fd *ofd) rdlen = sizeof(uint32_t) - esme->read_idx; rc = read(ofd->fd, lenptr + esme->read_idx, rdlen); if (rc < 0) - LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n", - esme->system_id, rc, strerror(errno)); + LOGPESME(esme, LOGL_ERROR, "read returned %zd (%s)\n", rc, strerror(errno)); OSMO_FD_CHECK_READ(rc, dead_socket); esme->read_idx += rc; @@ -839,8 +804,7 @@ static int esme_link_read_cb(struct osmo_fd *ofd) if (esme->read_idx >= sizeof(uint32_t)) { esme->read_len = ntohl(len); if (esme->read_len < 8 || esme->read_len > UINT16_MAX) { - LOGP(DSMPP, LOGL_ERROR, "[%s] length invalid %u\n", - esme->system_id, esme->read_len); + LOGPESME(esme, LOGL_ERROR, "length invalid %u\n", esme->read_len); goto dead_socket; } @@ -859,15 +823,15 @@ static int esme_link_read_cb(struct osmo_fd *ofd) rdlen = esme->read_len - esme->read_idx; rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg))); if (rc < 0) - LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %zd (%s)\n", - esme->system_id, rc, strerror(errno)); + LOGPESME(esme, LOGL_ERROR, "read returned %zd (%s)\n", + rc, strerror(errno)); OSMO_FD_CHECK_READ(rc, dead_socket); esme->read_idx += rc; msgb_put(msg, rc); if (esme->read_idx >= esme->read_len) { - rc = smpp_pdu_rx(esme, esme->read_msg); + rc = smpp_pdu_rx(e, esme->read_msg); msgb_free(esme->read_msg); esme->read_msg = NULL; esme->read_idx = 0; @@ -883,66 +847,77 @@ dead_socket: osmo_fd_unregister(&esme->wqueue.bfd); close(esme->wqueue.bfd.fd); esme->wqueue.bfd.fd = -1; - if (esme->acl) - esme->acl->esme = NULL; - smpp_esme_put(esme); + if (e->acl) + e->acl->esme = NULL; + smpp_esme_put(e); - return 0; + return -EBADF; } /* call-back of write queue once it wishes to write a message to the socket */ static int esme_link_write_cb(struct osmo_fd *ofd, struct msgb *msg) { - struct osmo_esme *esme = ofd->data; + struct smpp_esme *esme = ofd->data; int rc; rc = write(ofd->fd, msgb_data(msg), msgb_length(msg)); if (rc == 0) { - osmo_fd_unregister(&esme->wqueue.bfd); - close(esme->wqueue.bfd.fd); - esme->wqueue.bfd.fd = -1; + osmo_fd_unregister(&esme->esme->wqueue.bfd); + close(esme->esme->wqueue.bfd.fd); + esme->esme->wqueue.bfd.fd = -1; if (esme->acl) esme->acl->esme = NULL; smpp_esme_put(esme); } else if (rc < msgb_length(msg)) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id); + LOGPESME(esme->esme, LOGL_ERROR, "Short write\n"); return -1; } return 0; } +struct esme *esme_alloc(void *ctx) +{ + struct esme *e = talloc_zero(ctx, struct esme); + if (!e) + return NULL; + + e->own_seq_nr = rand(); + esme_inc_seq_nr(e); + osmo_wqueue_init(&e->wqueue, 10); + + return e; +} + /* callback for already-accepted new TCP socket */ static int link_accept_cb(struct smsc *smsc, int fd, struct sockaddr_storage *s, socklen_t s_len) { - struct osmo_esme *esme = talloc_zero(smsc, struct osmo_esme); + struct smpp_esme *esme = talloc_zero(smsc, struct smpp_esme); if (!esme) { close(fd); return -ENOMEM; } + esme->esme = esme_alloc(esme); + if (!esme->esme) { + close(fd); + return -ENOMEM; + } + INIT_LLIST_HEAD(&esme->smpp_cmd_list); smpp_esme_get(esme); - esme->own_seq_nr = rand(); - esme_inc_seq_nr(esme); esme->smsc = smsc; - osmo_wqueue_init(&esme->wqueue, 10); - esme->wqueue.bfd.fd = fd; - esme->wqueue.bfd.data = esme; - esme->wqueue.bfd.when = BSC_FD_READ; + osmo_fd_setup(&esme->esme->wqueue.bfd, fd, OSMO_FD_READ, osmo_wqueue_bfd_cb, esme, 0); - if (osmo_fd_register(&esme->wqueue.bfd) != 0) { + if (osmo_fd_register(&esme->esme->wqueue.bfd) != 0) { close(fd); talloc_free(esme); return -EIO; } - esme->wqueue.read_cb = esme_link_read_cb; - esme->wqueue.write_cb = esme_link_write_cb; - - esme->sa_len = OSMO_MIN(sizeof(esme->sa), s_len); - memcpy(&esme->sa, s, esme->sa_len); + esme->esme->wqueue.read_cb = esme_link_read_cb; + esme->esme->wqueue.write_cb = esme_link_write_cb; llist_add_tail(&esme->list, &smsc->esme_list); @@ -988,17 +963,21 @@ int smpp_smsc_conf(struct smsc *smsc, const char *bind_addr, uint16_t port) /* Avoid use-after-free if bind_addr == smsc->bind_addr */ if (smsc->bind_addr == bind_addr) return 0; + osmo_talloc_replace_string(smsc, &smsc->bind_addr, bind_addr); - talloc_free((void*)smsc->bind_addr); - smsc->bind_addr = NULL; - if (bind_addr) { - smsc->bind_addr = bind_addr ? talloc_strdup(smsc, bind_addr) : NULL; - if (!smsc->bind_addr) - return -ENOMEM; - } return 0; } +/*! /brief Close SMPP connection. */ +static void smpp_smsc_stop(struct smsc *smsc) +{ + if (smsc->listen_ofd.fd > 0) { + close(smsc->listen_ofd.fd); + smsc->listen_ofd.fd = 0; + osmo_fd_unregister(&smsc->listen_ofd); + } +} + /*! \brief Bind to given address and port and accept connections. * \param[in] bind_addr Local IP address, may be NULL for any. * \param[in] port TCP port number, may be 0 for default SMPP (2775). @@ -1007,23 +986,17 @@ int smpp_smsc_start(struct smsc *smsc, const char *bind_addr, uint16_t port) { int rc; - /* default port for SMPP */ - if (!port) - port = 2775; - - smpp_smsc_stop(smsc); - LOGP(DSMPP, LOGL_NOTICE, "SMPP at %s %d\n", - bind_addr? bind_addr : "0.0.0.0", port); + bind_addr ? bind_addr : "0.0.0.0", port ? port : SMPP_PORT); rc = osmo_sock_init_ofd(&smsc->listen_ofd, AF_UNSPEC, SOCK_STREAM, - IPPROTO_TCP, bind_addr, port, + IPPROTO_TCP, bind_addr, port ? port : SMPP_PORT, OSMO_SOCK_F_BIND); if (rc < 0) return rc; /* store new address and port */ - rc = smpp_smsc_conf(smsc, bind_addr, port); + rc = smpp_smsc_conf(smsc, bind_addr, port ? port : SMPP_PORT); if (rc) smpp_smsc_stop(smsc); return rc; @@ -1035,19 +1008,11 @@ int smpp_smsc_restart(struct smsc *smsc, const char *bind_addr, uint16_t port) { int rc; + smpp_smsc_stop(smsc); + rc = smpp_smsc_start(smsc, bind_addr, port); if (rc) /* if there is an error, try to re-bind to the old port */ return smpp_smsc_start(smsc, smsc->bind_addr, smsc->listen_port); return 0; } - -/*! /brief Close SMPP connection. */ -void smpp_smsc_stop(struct smsc *smsc) -{ - if (smsc->listen_ofd.fd > 0) { - close(smsc->listen_ofd.fd); - smsc->listen_ofd.fd = 0; - osmo_fd_unregister(&smsc->listen_ofd); - } -} diff --git a/src/libsmpputil/smpp_utils.c b/src/libsmpputil/smpp_utils.c new file mode 100644 index 000000000..bada97217 --- /dev/null +++ b/src/libsmpputil/smpp_utils.c @@ -0,0 +1,174 @@ + +/* (C) 2012-2022 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * 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 "config.h" + +#include <time.h> + +#include <osmocom/core/logging.h> +#include <osmocom/netif/stream.h> +#include <osmocom/smpp/smpp_smsc.h> + +/*! \brief retrieve SMPP command ID from a msgb */ +uint32_t smpp_msgb_cmdid(struct msgb *msg) +{ + uint8_t *tmp = msgb_data(msg) + 4; + return ntohl(*(uint32_t *)tmp); +} + +int smpp_determine_scheme(uint8_t dcs, uint8_t *data_coding, int *mode) +{ + if ((dcs & 0xF0) == 0xF0) { + if (dcs & 0x04) { + /* bit 2 == 1: 8bit data */ + *data_coding = 0x02; + *mode = MODE_8BIT; + } else { + /* bit 2 == 0: default alphabet */ + *data_coding = 0x01; + *mode = MODE_7BIT; + } + } else if ((dcs & 0xE0) == 0) { + switch (dcs & 0xC) { + case 0: + *data_coding = 0x01; + *mode = MODE_7BIT; + break; + case 4: + *data_coding = 0x02; + *mode = MODE_8BIT; + break; + case 8: + *data_coding = 0x08; /* UCS-2 */ + *mode = MODE_8BIT; + break; + default: + goto unknown_mo; + } + } else { +unknown_mo: + LOGP(DLSMS, LOGL_ERROR, "SMPP MO Unknown Data Coding 0x%02x\n", dcs); + return -1; + } + + return 0; + +} + +/* convert a 'struct tm' holding relative time to an absolute one by adding it to t_now */ +static void relative2absolute(struct tm *tm, time_t t_now) +{ + struct tm tm_now; + + localtime_r(&t_now, &tm_now); + + tm->tm_year += tm_now.tm_year; + tm->tm_mon += tm_now.tm_mon; + tm->tm_mday += tm_now.tm_mday; + tm->tm_hour += tm_now.tm_hour; + tm->tm_min += tm_now.tm_min; + tm->tm_sec += tm_now.tm_sec; +} + +#ifndef HAVE_TIMEGM +/* for systems without a timegm() function, provide a reimplementation */ +static time_t timegm(struct tm *tm) +{ + const char *orig_tz = getenv("TZ"); + time_t ret; + + setenv("TZ", "UTC", 1); + + ret = mktime(tm); + + if (orig_tz) + setenv("TZ", orig_tz, 1); + else + unsetenv("TZ"); + + return ret; +} +#endif + + +/*! Parse a SMPP time format as defined in SMPP v3.4 7.1.1. + * \param[in] vp string containing the time as encoded in SMPP v3.4 + * \param[in] t_now pointer to a time value for 'now'. Can be NULL, then we call time() ourselves. + * \returns time_t value in seconds since the epoch of the absolute decoded time */ +time_t smpp_parse_time_format(const char *vp, time_t *t_now) +{ + unsigned int year, month, day, hour, minute, second, tenth, gmt_off_quarter; + char plus_minus_relative; + int gmt_off_minutes; + struct tm tm; + time_t ret; + int rc; + + memset(&tm, 0, sizeof(tm)); + + if (vp[0] == '\0') + return 0; + + /* YYMMDDhhmmsstnnp (where p can be -, + or R) */ + rc = sscanf(vp, "%2u%2u%2u%2u%2u%2u%1u%2u%c", &year, &month, &day, &hour, &minute, + &second, &tenth, &gmt_off_quarter, &plus_minus_relative); + if (rc != 9) + return (time_t) -1; + + tm.tm_year = year; + /* month handling differs between absolute/relative below... */ + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = minute; + tm.tm_sec = second; + tm.tm_isdst = 0; + + switch (plus_minus_relative) { + case '+': /* time is in quarter hours advanced compared to UTC */ + if (year < 70) + tm.tm_year += 100; + tm.tm_mon = month - 1; + gmt_off_minutes = 15 * gmt_off_quarter; + tm.tm_min -= gmt_off_minutes; + ret = timegm(&tm); + break; + case '-': /* time is in quarter hours retared compared to UTC */ + if (year < 70) + tm.tm_year += 100; + tm.tm_mon = month - 1; + gmt_off_minutes = 15 * gmt_off_quarter; + tm.tm_min += gmt_off_minutes; + ret = timegm(&tm); + break; + case 'R': + /* relative time */ + tm.tm_mon = month; + if (t_now) + relative2absolute(&tm, *t_now); + else + relative2absolute(&tm, time(NULL)); + /* here we do want local time, as we're passing local time in above! */ + ret = mktime(&tm); + break; + default: + return (time_t) -1; + } + + return ret; +} diff --git a/src/libmsc/smpp_vty.c b/src/libsmpputil/smpp_vty.c index 9026f6cf5..c6e642161 100644 --- a/src/libmsc/smpp_vty.c +++ b/src/libsmpputil/smpp_vty.c @@ -30,11 +30,11 @@ #include <osmocom/core/linuxlist.h> #include <osmocom/core/utils.h> +#include <osmocom/core/socket.h> #include <osmocom/core/talloc.h> #include <osmocom/msc/vty.h> - -#include "smpp_smsc.h" +#include <osmocom/smpp/smpp_smsc.h> struct smsc *smsc_from_vty(struct vty *v); @@ -80,8 +80,8 @@ static int smpp_local_tcp(struct vty *vty, const char *bind_addr, uint16_t port) { struct smsc *smsc = smsc_from_vty(vty); - int is_running = smsc->listen_ofd.fd > 0; - int same_bind_addr; + bool is_running = smsc->listen_ofd.fd > 0; + bool same_bind_addr; int rc; /* If it is not up yet, don't rebind, just set values. */ @@ -501,7 +501,7 @@ DEFUN(cfg_esme_no_dcs_transp, cfg_esme_no_dcs_transp_cmd, DEFUN(cfg_esme_alert_notif, cfg_esme_alert_notif_cmd, "alert-notifications", - "Disable sending of SMPP Alert Notifications for this ESME") + "Enable sending of SMPP Alert Notifications for this ESME") { struct osmo_smpp_acl *acl = vty->index; @@ -522,19 +522,12 @@ DEFUN(cfg_esme_no_alert_notif, cfg_esme_no_alert_notif_cmd, } -static void dump_one_esme(struct vty *vty, struct osmo_esme *esme) +static void dump_one_esme(struct vty *vty, struct smpp_esme *esme) { - char host[128], serv[128]; - - host[0] = 0; - serv[0] = 0; - getnameinfo((const struct sockaddr *) &esme->sa, esme->sa_len, - host, sizeof(host), serv, sizeof(serv), NI_NUMERICSERV); - vty_out(vty, "ESME System ID: %s, Password: %s, SMPP Version %02x%s", - esme->system_id, esme->acl ? esme->acl->passwd : "", + esme->esme->system_id, esme->acl ? esme->acl->passwd : "", esme->smpp_version, VTY_NEWLINE); - vty_out(vty, " Connected from: %s:%s%s", host, serv, VTY_NEWLINE); + vty_out(vty, " Connection %s%s", osmo_sock_get_name(tall_vty_ctx, esme->esme->wqueue.bfd.fd), VTY_NEWLINE); if (esme->smsc->def_route == esme->acl) vty_out(vty, " Is current default route%s", VTY_NEWLINE); } @@ -544,7 +537,7 @@ DEFUN(show_esme, show_esme_cmd, SHOW_STR "SMPP Interface\n" "SMPP External SMS Entity\n") { struct smsc *smsc = smsc_from_vty(vty); - struct osmo_esme *esme; + struct smpp_esme *esme; llist_for_each_entry(esme, &smsc->esme_list, list) dump_one_esme(vty, esme); diff --git a/src/libvlr/vlr.c b/src/libvlr/vlr.c index ca6f90de7..cb5794f30 100644 --- a/src/libvlr/vlr.c +++ b/src/libvlr/vlr.c @@ -23,7 +23,9 @@ #include <osmocom/core/fsm.h> #include <osmocom/core/utils.h> #include <osmocom/core/timer.h> +#include <osmocom/core/tdef.h> #include <osmocom/gsm/protocol/gsm_04_08_gprs.h> +#include <osmocom/gsm/gsm23236.h> #include <osmocom/gsm/gsup.h> #include <osmocom/gsm/apn.h> #include <osmocom/gsm/gsm48.h> @@ -34,6 +36,7 @@ #include <osmocom/msc/vlr.h> #include <osmocom/msc/debug.h> #include <osmocom/msc/gsup_client_mux.h> +#include <osmocom/msc/paging.h> #include <netinet/in.h> #include <arpa/inet.h> @@ -49,10 +52,139 @@ #define SGSN_SUBSCR_MAX_RETRIES 3 #define SGSN_SUBSCR_RETRY_INTERVAL 10 +enum vlr_stat_item_idx { + VLR_STAT_SUBSCRIBER_COUNT, + VLR_STAT_PDP_COUNT, +}; + +static const struct osmo_stat_item_desc vlr_stat_item_desc[] = { + [VLR_STAT_SUBSCRIBER_COUNT] = { "subscribers", + "Number of subscribers present in VLR" }, + [VLR_STAT_PDP_COUNT] = { "pdp", + "Number of PDP records present in VLR" }, +}; + +static const struct osmo_stat_item_group_desc vlr_statg_desc = { + "vlr", + "visitor location register", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(vlr_stat_item_desc), + vlr_stat_item_desc, +}; + +enum vlr_rate_ctr_idx { + VLR_CTR_GSUP_RX_UNKNOWN_IMSI, + VLR_CTR_GSUP_RX_PURGE_NO_SUBSCR, + VLR_CTR_GSUP_RX_TUPLES, + VLR_CTR_GSUP_RX_UL_RES, + VLR_CTR_GSUP_RX_UL_ERR, + VLR_CTR_GSUP_RX_SAI_RES, + VLR_CTR_GSUP_RX_SAI_ERR, + VLR_CTR_GSUP_RX_ISD_REQ, + VLR_CTR_GSUP_RX_CANCEL_REQ, + VLR_CTR_GSUP_RX_CHECK_IMEI_RES, + VLR_CTR_GSUP_RX_CHECK_IMEI_ERR, + VLR_CTR_GSUP_RX_PURGE_MS_RES, + VLR_CTR_GSUP_RX_PURGE_MS_ERR, + VLR_CTR_GSUP_RX_DELETE_DATA_REQ, + VLR_CTR_GSUP_RX_UNKNOWN, + + VLR_CTR_GSUP_TX_UL_REQ, + VLR_CTR_GSUP_TX_ISD_RES, + VLR_CTR_GSUP_TX_SAI_REQ, + VLR_CTR_GSUP_TX_PURGE_MS_REQ, + VLR_CTR_GSUP_TX_CHECK_IMEI_REQ, + VLR_CTR_GSUP_TX_AUTH_FAIL_REP, + VLR_CTR_GSUP_TX_CANCEL_RES, + + VLR_CTR_DETACH_BY_REQ, + VLR_CTR_DETACH_BY_CANCEL, + VLR_CTR_DETACH_BY_T3212, +}; + +static const struct rate_ctr_desc vlr_ctr_desc[] = { + [VLR_CTR_GSUP_RX_UNKNOWN_IMSI] = { "gsup:rx:unknown_imsi", + "Received GSUP messages for unknown IMSI" }, + [VLR_CTR_GSUP_RX_PURGE_NO_SUBSCR] = { "gsup:rx:purge_no_subscr", + "Received GSUP purge for unknown subscriber" }, + [VLR_CTR_GSUP_RX_TUPLES] = { "gsup:rx:auth_tuples", + "Received GSUP authentication tuples" }, + [VLR_CTR_GSUP_RX_UL_RES] = { "gsup:rx:upd_loc:res", + "Received GSUP Update Location Result messages" }, + [VLR_CTR_GSUP_RX_UL_ERR] = { "gsup:rx:upd_loc:err", + "Received GSUP Update Location Error messages" }, + [VLR_CTR_GSUP_RX_SAI_RES] = { "gsup:rx:send_auth_info:res", + "Received GSUP Send Auth Info Result messages" }, + [VLR_CTR_GSUP_RX_SAI_ERR] = { "gsup:rx:send_auth_info:err", + "Received GSUP Send Auth Info Error messages" }, + [VLR_CTR_GSUP_RX_ISD_REQ] = { "gsup:rx:ins_sub_data:req", + "Received GSUP Insert Subscriber Data Request messages" }, + [VLR_CTR_GSUP_RX_CANCEL_REQ] = { "gsup:rx:cancel:req", + "Received GSUP Cancel Subscriber messages" }, + [VLR_CTR_GSUP_RX_CHECK_IMEI_RES] = { "gsup:rx:check_imei:res", + "Received GSUP Check IMEI Result messages" }, + [VLR_CTR_GSUP_RX_CHECK_IMEI_ERR] = { "gsup:rx:check_imei:err", + "Received GSUP Check IMEI Error messages" }, + [VLR_CTR_GSUP_RX_PURGE_MS_RES] = { "gsup:rx:purge_ms:res", + "Received GSUP Purge MS Result messages" }, + [VLR_CTR_GSUP_RX_PURGE_MS_ERR] = { "gsup:rx:purge_ms:err", + "Received GSUP Purge MS Error messages" }, + [VLR_CTR_GSUP_RX_DELETE_DATA_REQ] = { "gsup:rx:del_sub_data:req", + "Received GSUP Delete Subscriber Data Request messages" }, + [VLR_CTR_GSUP_RX_UNKNOWN] = { "gsup:rx:unknown_msgtype", + "Received GSUP message of unknown type" }, + + [VLR_CTR_GSUP_TX_UL_REQ] = { "gsup:tx:upd_loc:req", + "Transmitted GSUP Update Location Request messages" }, + [VLR_CTR_GSUP_TX_ISD_RES] = { "gsup:tx:ins_sub_data:res", + "Transmitted GSUP Insert Subscriber Data Result messages" }, + [VLR_CTR_GSUP_TX_SAI_REQ] = { "gsup:tx:send_auth_info:res", + "Transmitted GSUP Send Auth Info Request messages" }, + [VLR_CTR_GSUP_TX_PURGE_MS_REQ] = { "gsup:tx:purge_ms:req", + "Transmitted GSUP Purge MS Request messages" }, + [VLR_CTR_GSUP_TX_CHECK_IMEI_REQ] = { "gsup:tx:check_imei:req", + "Transmitted GSUP Check IMEI Request messages" }, + [VLR_CTR_GSUP_TX_AUTH_FAIL_REP] = { "gsup:tx:auth_fail:rep", + "Transmitted GSUP Auth Fail Report messages" }, + [VLR_CTR_GSUP_TX_CANCEL_RES] = { "gsup:tx:cancel:res", + "Transmitted GSUP Cancel Result messages" }, + + [VLR_CTR_DETACH_BY_REQ] = { "detach:imsi_det_req", + "VLR Subscriber Detach by IMSI DETACH REQ" }, + [VLR_CTR_DETACH_BY_CANCEL] = { "detach:gsup_cancel_req", + "VLR Subscriber Detach by GSUP CANCEL REQ" }, + [VLR_CTR_DETACH_BY_T3212] = { "detach:t3212_timeout", + "VLR Subscriber Detach by T3212 timeout" }, +}; + +static const struct rate_ctr_group_desc vlr_ctrg_desc = { + "vlr", + "visitor location register", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(vlr_ctr_desc), + vlr_ctr_desc, +}; + + +#define vlr_rate_ctr_inc(vlr, idx) \ + rate_ctr_inc(rate_ctr_group_get_ctr((vlr)->ctrg, idx)) +#define vlr_rate_ctr_add(vlr, idx, val) \ + rate_ctr_add(rate_ctr_group_get_ctr((vlr)->ctrg, idx), val) + +#define vlr_stat_item_inc(vlr, idx) \ + osmo_stat_item_inc(osmo_stat_item_group_get_item((vlr)->statg, idx), 1) +#define vlr_stat_item_dec(vlr, idx) \ + osmo_stat_item_dec(osmo_stat_item_group_get_item((vlr)->statg, idx), 1) +#define vlr_stat_item_set(vlr, idx, val) \ + osmo_stat_item_set(osmo_stat_item_group_get_item((vlr)->statg, idx), val) + + /*********************************************************************** * Convenience functions ***********************************************************************/ +static int vlr_subscr_detach(struct vlr_subscr *vsub); + const struct value_string vlr_ciph_names[] = { OSMO_VALUE_STRING(VLR_CIPH_NONE), OSMO_VALUE_STRING(VLR_CIPH_A5_1), @@ -61,24 +193,23 @@ const struct value_string vlr_ciph_names[] = { { 0, NULL } }; +/* 3GPP TS 24.008, table 11.2 Mobility management timers (network-side) */ +struct osmo_tdef msc_tdefs_vlr[] = { + { .T = 3212, .default_val = 60, .unit = OSMO_TDEF_M, .desc = "Subscriber expiration timeout" }, + { .T = 3250, .default_val = 12, .desc = "TMSI Reallocation procedure" }, + { .T = 3260, .default_val = 12, .desc = "Authentication procedure" }, + { .T = 3270, .default_val = 12, .desc = "Identification procedure" }, + { /* terminator */ } +}; + +/* This is just a wrapper around the osmo_tdef API. + * TODO: we should start using osmo_tdef_fsm_inst_state_chg() */ uint32_t vlr_timer(struct vlr_instance *vlr, uint32_t timer) { - uint32_t tidx = 0xffffffff; - - switch (timer) { - case 3270: - tidx = VLR_T_3270; - break; - case 3260: - tidx = VLR_T_3260; - break; - case 3250: - tidx = VLR_T_3250; - break; - } - - OSMO_ASSERT(tidx < sizeof(vlr->cfg.timer)); - return vlr->cfg.timer[tidx]; + /* NOTE: since we usually do not need more than one instance of the VLR, + * and since libosmocore's osmo_tdef API does not (yet) support dynamic + * configuration, we always use the global instance of msc_tdefs_vlr. */ + return osmo_tdef_get(msc_tdefs_vlr, timer, OSMO_TDEF_S, 0); } /* return static buffer with printable name of VLR subscriber */ @@ -142,7 +273,8 @@ struct vlr_subscr *_vlr_subscr_find_by_imsi(struct vlr_instance *vlr, llist_for_each_entry(vsub, &vlr->subscribers, list) { if (vlr_subscr_matches_imsi(vsub, imsi)) { - vlr_subscr_get_src(vsub, use, file, line); + if (use) + vlr_subscr_get_src(vsub, use, file, line); return vsub; } } @@ -187,6 +319,21 @@ struct vlr_subscr *_vlr_subscr_find_by_msisdn(struct vlr_instance *vlr, return NULL; } +struct vlr_subscr *_vlr_subscr_find_by_mi(struct vlr_instance *vlr, + const struct osmo_mobile_identity *mi, + const char *use, + const char *file, int line) +{ + switch (mi->type) { + case GSM_MI_TYPE_IMSI: + return _vlr_subscr_find_by_imsi(vlr, mi->imsi, use, file, line); + case GSM_MI_TYPE_TMSI: + return _vlr_subscr_find_by_tmsi(vlr, mi->tmsi, use, file, line); + default: + return NULL; + } +} + /* Transmit GSUP message for subscriber to HLR, using IMSI from subscriber */ static int vlr_subscr_tx_gsup_message(const struct vlr_subscr *vsub, struct osmo_gsup_message *gsup_msg) @@ -264,6 +411,7 @@ static struct vlr_subscr *_vlr_subscr_alloc(struct vlr_instance *vlr) vlr_sgs_fsm_create(vsub); llist_add_tail(&vsub->list, &vlr->subscribers); + vlr_stat_item_inc(vlr, VLR_STAT_SUBSCRIBER_COUNT); return vsub; } @@ -274,6 +422,8 @@ int vlr_subscr_purge(struct vlr_subscr *vsub) { struct osmo_gsup_message gsup_msg = {0}; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_PURGE_MS_REQ); + gsup_msg.message_type = OSMO_GSUP_MSGT_PURGE_MS_REQUEST; /* provide HLR number in case we know it */ @@ -302,6 +452,7 @@ void vlr_subscr_cancel_attach_fsm(struct vlr_subscr *vsub, void vlr_subscr_free(struct vlr_subscr *vsub) { llist_del(&vsub->list); + vlr_stat_item_dec(vsub->vlr, VLR_STAT_SUBSCRIBER_COUNT); DEBUGP(DVLR, "freeing VLR subscr %s (max total use count was %d)\n", vlr_subscr_name(vsub), vsub->max_total_use_count); @@ -329,6 +480,15 @@ int vlr_subscr_alloc_tmsi(struct vlr_subscr *vsub) LOGP(DDB, LOGL_ERROR, "osmo_get_rand_id() failed: %s\n", strerror(-rc)); return rc; } + + if (!llist_empty(&vlr->cfg.nri_ranges->entries)) { + int16_t nri_v; + osmo_tmsi_nri_v_limit_by_ranges(&tmsi, vlr->cfg.nri_ranges, vlr->cfg.nri_bitlen); + osmo_tmsi_nri_v_get(&nri_v, tmsi, vlr->cfg.nri_bitlen); + LOGP(DVLR, LOGL_DEBUG, "New NRI from range [%s] = 0x%x --> TMSI 0x%08x\n", + osmo_nri_ranges_to_str_c(OTC_SELECT, vlr->cfg.nri_ranges), nri_v, tmsi); + } + /* throw the dice again, if the TSMI doesn't fit */ if (tmsi == GSM_RESERVED_TMSI) continue; @@ -360,7 +520,7 @@ int vlr_subscr_alloc_tmsi(struct vlr_subscr *vsub) } /* Find subscriber by IMSI, or create new subscriber if not found. - * \param[in] vlr VLR instace. + * \param[in] vlr VLR instance. * \param[in] imsi IMSI string. * \param[out] created if non-NULL, returns whether a new entry was created. */ struct vlr_subscr *_vlr_subscr_find_or_create_by_imsi(struct vlr_instance *vlr, @@ -390,7 +550,7 @@ struct vlr_subscr *_vlr_subscr_find_or_create_by_imsi(struct vlr_instance *vlr, } /* Find subscriber by TMSI, or create new subscriber if not found. - * \param[in] vlr VLR instace. + * \param[in] vlr VLR instance. * \param[in] tmsi TMSI. * \param[out] created if non-NULL, returns whether a new entry was created. */ struct vlr_subscr *_vlr_subscr_find_or_create_by_tmsi(struct vlr_instance *vlr, @@ -419,11 +579,63 @@ struct vlr_subscr *_vlr_subscr_find_or_create_by_tmsi(struct vlr_instance *vlr, return vsub; } +static void dedup_vsub(struct vlr_subscr *exists, struct vlr_subscr *vsub) +{ + struct vlr_instance *vlr = exists->vlr; + int i; + int j; + LOGP(DVLR, LOGL_NOTICE, + "There is an existing subscriber for IMSI %s used by %s, replacing with new VLR subscr: %s used by %s\n", + exists->imsi, osmo_use_count_to_str_c(OTC_SELECT, &exists->use_count), + vlr_subscr_name(vsub), + osmo_use_count_to_str_c(OTC_SELECT, &vsub->use_count)); + + /* Take over some state from the previous vsub */ + paging_request_join_vsub(vsub, exists); + if (!vsub->msisdn[0]) + OSMO_STRLCPY_ARRAY(vsub->msisdn, exists->msisdn); + if (!vsub->name[0]) + OSMO_STRLCPY_ARRAY(vsub->name, exists->name); + /* Copy valid auth tuples we may already have, to reduce the need to ask for new ones from the HLR */ + for (i = 0; i < ARRAY_SIZE(exists->auth_tuples); i++) { + if (exists->auth_tuples[i].key_seq == VLR_KEY_SEQ_INVAL) + continue; + for (j = 0; j < ARRAY_SIZE(vsub->auth_tuples); j++) { + if (vsub->auth_tuples[j].key_seq != VLR_KEY_SEQ_INVAL) + continue; + vsub->auth_tuples[j] = exists->auth_tuples[i]; + } + } + + if (exists->msc_conn_ref) + LOGVSUBP(LOGL_ERROR, vsub, + "There is an existing VLR entry for this same subscriber with an active connection." + " That should not be possible. Discarding old subscriber entry %s.\n", + exists->imsi); + + if (vlr->ops.subscr_inval) + vlr->ops.subscr_inval(exists->msc_conn_ref, exists); + vlr_subscr_free(exists); +} + void vlr_subscr_set_imsi(struct vlr_subscr *vsub, const char *imsi) { + struct vlr_subscr *exists; if (!vsub) return; + /* If the same IMSI is already set, nothing changes. */ + if (!strcmp(vsub->imsi, imsi)) + return; + + /* We've just learned about this new IMSI, our primary key in the VLR. make sure to invalidate any prior VLR + * entries for this IMSI. */ + exists = vlr_subscr_find_by_imsi(vsub->vlr, imsi, NULL); + + if (exists) + dedup_vsub(exists, vsub); + + /* Set the IMSI on the new subscriber, here. */ if (OSMO_STRLCPY_ARRAY(vsub->imsi, imsi) >= sizeof(vsub->imsi)) { LOGP(DVLR, LOGL_NOTICE, "IMSI was truncated: full IMSI=%s, truncated IMSI=%s\n", imsi, vsub->imsi); @@ -466,6 +678,23 @@ void vlr_subscr_set_msisdn(struct vlr_subscr *vsub, const char *msisdn) vsub->imsi, vsub->msisdn); } +void vlr_subscr_set_last_used_eutran_plmn_id(struct vlr_subscr *vsub, + const struct osmo_plmn_id *last_eutran_plmn) +{ + if (!vsub) + return; + if (last_eutran_plmn) { + vsub->sgs.last_eutran_plmn_present = true; + memcpy(&vsub->sgs.last_eutran_plmn, last_eutran_plmn, sizeof(*last_eutran_plmn)); + } else { + vsub->sgs.last_eutran_plmn_present = false; + } + DEBUGP(DVLR, "set Last E-UTRAN PLMN ID on subscriber: %s\n", + vsub->sgs.last_eutran_plmn_present ? + osmo_plmn_name(&vsub->sgs.last_eutran_plmn) : + "(none)"); +} + bool vlr_subscr_matches_imsi(struct vlr_subscr *vsub, const char *imsi) { return vsub && imsi && vsub->imsi[0] && !strcmp(vsub->imsi, imsi); @@ -499,14 +728,11 @@ int vlr_subscr_changed(struct vlr_subscr *vsub) void vlr_subscr_enable_expire_lu(struct vlr_subscr *vsub) { - struct gsm_network *net = vsub->vlr->user_ctx; /* XXX move t3212 into struct vlr_instance? */ struct timespec now; - /* The T3212 timeout value field is coded as the binary representation of the timeout - * value for periodic updating in decihours. Mark the subscriber as inactive if it missed - * two consecutive location updates. Timeout is twice the t3212 value plus one minute. */ + /* Mark the subscriber as inactive if it stopped to do periodical location updates. */ if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) == 0) { - vsub->expire_lu = now.tv_sec + (net->t3212 * 60 * 6 * 2) + 60; + vsub->expire_lu = now.tv_sec + vlr_timer(vsub->vlr, 3212); } else { LOGP(DVLR, LOGL_ERROR, "%s: Could not enable Location Update expiry: unable to read current time\n", vlr_subscr_name(vsub)); @@ -519,13 +745,11 @@ void vlr_subscr_expire_lu(void *data) { struct vlr_instance *vlr = data; struct vlr_subscr *vsub, *vsub_tmp; - struct gsm_network *net; struct timespec now; /* Periodic location update might be disabled from the VTY, * so we shall not expire subscribers until explicit IMSI Detach. */ - net = vlr->user_ctx; /* XXX move t3212 into struct vlr_instance? */ - if (!net->t3212) + if (!vlr_timer(vlr, 3212)) goto done; if (llist_empty(&vlr->subscribers)) @@ -541,7 +765,8 @@ void vlr_subscr_expire_lu(void *data) continue; LOGP(DVLR, LOGL_DEBUG, "%s: Location Update expired\n", vlr_subscr_name(vsub)); - vlr_subscr_rx_imsi_detach(vsub); + vlr_rate_ctr_inc(vlr, VLR_CTR_DETACH_BY_T3212); + vlr_subscr_detach(vsub); } done: @@ -557,13 +782,15 @@ done: /* see GSM 09.02, 17.7.1, PDP-Context and GPRSSubscriptionData */ /* see GSM 09.02, B.1, gprsSubscriptionData */ struct sgsn_subscriber_pdp_data { - struct llist_head list; - - unsigned int context_id; - uint16_t pdp_type; - char apn_str[GSM_APN_LENGTH]; - uint8_t qos_subscribed[20]; - size_t qos_subscribed_len; + struct llist_head list; + + unsigned int context_id; + enum gsm48_pdp_type_org pdp_type_org; + enum gsm48_pdp_type_nr pdp_type_nr; + struct osmo_sockaddr pdp_address[2]; + char apn_str[GSM_APN_LENGTH]; + uint8_t qos_subscribed[20]; + size_t qos_subscribed_len; }; struct sgsn_subscriber_pdp_data * @@ -574,6 +801,7 @@ vlr_subscr_pdp_data_alloc(struct vlr_subscr *vsub) pdata = talloc_zero(vsub, struct sgsn_subscriber_pdp_data); llist_add_tail(&pdata->list, &vsub->ps.pdp_list); + vlr_stat_item_inc(vsub->vlr, VLR_STAT_PDP_COUNT); return pdata; } @@ -585,6 +813,7 @@ static int vlr_subscr_pdp_data_clear(struct vlr_subscr *vsub) llist_for_each_entry_safe(pdp, pdp2, &vsub->ps.pdp_list, list) { llist_del(&pdp->list); + vlr_stat_item_dec(vsub->vlr, VLR_STAT_PDP_COUNT); talloc_free(pdp); count += 1; } @@ -655,6 +884,8 @@ int vlr_subscr_req_lu(struct vlr_subscr *vsub) struct osmo_gsup_message gsup_msg = {0}; int rc; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_UL_REQ); + gsup_msg.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST; gsup_msg.cn_domain = vsub->vlr->cfg.is_ps ? OSMO_GSUP_CN_DOMAIN_PS : OSMO_GSUP_CN_DOMAIN_CS; rc = vlr_subscr_tx_gsup_message(vsub, &gsup_msg); @@ -668,9 +899,12 @@ int vlr_subscr_req_sai(struct vlr_subscr *vsub, { struct osmo_gsup_message gsup_msg = {0}; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_SAI_REQ); + gsup_msg.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST; gsup_msg.auts = auts; gsup_msg.rand = auts_rand; + gsup_msg.cn_domain = OSMO_GSUP_CN_DOMAIN_CS; return vlr_subscr_tx_gsup_message(vsub, &gsup_msg); } @@ -694,6 +928,8 @@ int vlr_subscr_tx_req_check_imei(const struct vlr_subscr *vsub) gsup_msg.imei_enc = imei_enc; gsup_msg.imei_enc_len = len; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_CHECK_IMEI_REQ); + /* Send CHECK_IMEI_REQUEST */ OSMO_STRLCPY_ARRAY(gsup_msg.imsi, vsub->imsi); return gsup_client_mux_tx(vsub->vlr->gcm, &gsup_msg); @@ -707,6 +943,8 @@ int vlr_subscr_tx_auth_fail_rep(const struct vlr_subscr *vsub) .message_type = OSMO_GSUP_MSGT_AUTH_FAIL_REPORT, }; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_AUTH_FAIL_REP); + OSMO_STRLCPY_ARRAY(gsup_msg.imsi, vsub->imsi); return gsup_client_mux_tx(vsub->vlr->gcm, &gsup_msg); } @@ -730,7 +968,7 @@ void vlr_subscr_update_tuples(struct vlr_subscr *vsub, if (key_seq >= ARRAY_SIZE(vsub->auth_tuples)) { LOGVSUBP(LOGL_NOTICE, vsub, - "Skipping auth tuple wih invalid cksn %zu\n", + "Skipping auth tuple with invalid cksn %zu\n", key_seq); continue; } @@ -740,6 +978,7 @@ void vlr_subscr_update_tuples(struct vlr_subscr *vsub, } LOGVSUBP(LOGL_DEBUG, vsub, "Received %u auth tuples\n", got_tuples); + vlr_rate_ctr_add(vsub->vlr, VLR_CTR_GSUP_RX_TUPLES, got_tuples); if (!got_tuples) { /* FIXME what now? */ @@ -783,7 +1022,7 @@ static void vlr_subscr_gsup_insert_data(struct vlr_subscr *vsub, unsigned idx; int rc; - if (gsup_msg->msisdn_enc) {//FIXME: vlr_subscr_set_msisdn()? + if (gsup_msg->msisdn_enc_len) {//FIXME: vlr_subscr_set_msisdn()? gsm48_decode_bcd_number2(vsub->msisdn, sizeof(vsub->msisdn), gsup_msg->msisdn_enc, gsup_msg->msisdn_enc_len, 0); @@ -841,7 +1080,10 @@ static void vlr_subscr_gsup_insert_data(struct vlr_subscr *vsub, } OSMO_ASSERT(pdp_data != NULL); - pdp_data->pdp_type = pdp_info->pdp_type; + pdp_data->pdp_type_org = pdp_info->pdp_type_org; + pdp_data->pdp_type_nr = pdp_info->pdp_type_nr; + memcpy(&pdp_data->pdp_address[0], &pdp_info->pdp_address[0], sizeof(pdp_data->pdp_address[0])); + memcpy(&pdp_data->pdp_address[1], &pdp_info->pdp_address[1], sizeof(pdp_data->pdp_address[1])); osmo_apn_to_str(pdp_data->apn_str, pdp_info->apn_enc, pdp_info->apn_enc_len); memcpy(pdp_data->qos_subscribed, pdp_info->qos_enc, pdp_info->qos_enc_len); @@ -856,6 +1098,8 @@ static int vlr_subscr_handle_isd_req(struct vlr_subscr *vsub, { struct osmo_gsup_message gsup_reply = {0}; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_ISD_RES); + vlr_subscr_gsup_insert_data(vsub, gsup); vsub->vlr->ops.subscr_update(vsub); @@ -1022,6 +1266,8 @@ static int vlr_subscr_handle_cancel_req(struct vlr_subscr *vsub, int rc, is_update_procedure = !gsup_msg->cancel_type || gsup_msg->cancel_type == OSMO_GSUP_CANCEL_TYPE_UPDATE; + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_GSUP_TX_CANCEL_RES); + LOGVSUBP(LOGL_INFO, vsub, "Cancelling MS subscriber (%s)\n", is_update_procedure ? "update procedure" : "subscription withdraw"); @@ -1032,7 +1278,8 @@ static int vlr_subscr_handle_cancel_req(struct vlr_subscr *vsub, vlr_gmm_cause_to_mm_cause(gsup_msg->cause, &gsm48_rej); vlr_subscr_cancel_attach_fsm(vsub, fsm_cause, gsm48_rej); - vlr_subscr_rx_imsi_detach(vsub); + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_DETACH_BY_CANCEL); + vlr_subscr_detach(vsub); return rc; } @@ -1068,49 +1315,65 @@ int vlr_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_ { struct vlr_instance *vlr = data; struct vlr_subscr *vsub; - int rc; + int rc = 0; vsub = vlr_subscr_find_by_imsi(vlr, gsup->imsi, __func__); if (!vsub) { switch (gsup->message_type) { case OSMO_GSUP_MSGT_PURGE_MS_RESULT: case OSMO_GSUP_MSGT_PURGE_MS_ERROR: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_PURGE_NO_SUBSCR); return vlr_rx_gsup_purge_no_subscr(vlr, gsup); default: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UNKNOWN_IMSI); return vlr_rx_gsup_unknown_imsi(vlr, gsup); } } switch (gsup->message_type) { case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_SAI_RES); + rc = vlr_subscr_handle_sai_res(vsub, gsup); + break; case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_SAI_ERR); rc = vlr_subscr_handle_sai_res(vsub, gsup); break; case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_ISD_REQ); rc = vlr_subscr_handle_isd_req(vsub, gsup); break; case OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_CANCEL_REQ); rc = vlr_subscr_handle_cancel_req(vsub, gsup); break; case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UL_RES); rc = vlr_subscr_handle_lu_res(vsub, gsup); break; case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UL_ERR); rc = vlr_subscr_handle_lu_err(vsub, gsup); break; case OSMO_GSUP_MSGT_PURGE_MS_ERROR: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_PURGE_MS_ERR); + goto out_unimpl; case OSMO_GSUP_MSGT_PURGE_MS_RESULT: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_PURGE_MS_RES); + goto out_unimpl; case OSMO_GSUP_MSGT_DELETE_DATA_REQUEST: - LOGVSUBP(LOGL_ERROR, vsub, - "Rx GSUP msg_type=%d not yet implemented\n", - gsup->message_type); - rc = -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; - break; + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_DELETE_DATA_REQ); + goto out_unimpl; case OSMO_GSUP_MSGT_CHECK_IMEI_ERROR: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_CHECK_IMEI_ERR); + rc = vlr_subscr_handle_check_imei(vsub, gsup); + break; case OSMO_GSUP_MSGT_CHECK_IMEI_RESULT: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_CHECK_IMEI_RES); rc = vlr_subscr_handle_check_imei(vsub, gsup); break; default: + vlr_rate_ctr_inc(vlr, VLR_CTR_GSUP_RX_UNKNOWN); LOGP(DLGSUP, LOGL_ERROR, "GSUP Message type not handled by VLR: %d\n", gsup->message_type); rc = -EINVAL; break; @@ -1118,66 +1381,57 @@ int vlr_gsup_rx(struct gsup_client_mux *gcm, void *data, const struct osmo_gsup_ vlr_subscr_put(vsub, __func__); return rc; + +out_unimpl: + LOGVSUBP(LOGL_ERROR, vsub, "Rx GSUP msg_type=%d not yet implemented\n", gsup->message_type); + vlr_subscr_put(vsub, __func__); + return -GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL; } /* MSC->VLR: Subscriber has provided IDENTITY RESPONSE */ -int vlr_subscr_rx_id_resp(struct vlr_subscr *vsub, - const uint8_t *mi, size_t mi_len) +int vlr_subscr_rx_id_resp(struct vlr_subscr *vsub, const struct osmo_mobile_identity *mi) { - char mi_string[GSM48_MI_SIZE]; - uint8_t mi_type = mi[0] & GSM_MI_TYPE_MASK; - - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len); - /* update the vlr_subscr with the given identity */ - switch (mi_type) { + switch (mi->type) { case GSM_MI_TYPE_IMSI: - if (strlen(mi_string) >= sizeof(vsub->imsi)) { - LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP too long (>%zu bytes): %s\n", - sizeof(vsub->imsi) - 1, mi_string); - return -ENOSPC; /* ignore message; do not avance LU FSM */ - } else if (vsub->imsi[0] - && !vlr_subscr_matches_imsi(vsub, mi_string)) { + if (vsub->imsi[0] + && !vlr_subscr_matches_imsi(vsub, mi->imsi)) { LOGVSUBP(LOGL_ERROR, vsub, "IMSI in ID RESP differs:" - " %s\n", mi_string); + " %s\n", mi->imsi); /* XXX Should we return an error, e.g. -EINVAL ? */ } else - vlr_subscr_set_imsi(vsub, mi_string); + vlr_subscr_set_imsi(vsub, mi->imsi); break; case GSM_MI_TYPE_IMEI: - vlr_subscr_set_imei(vsub, mi_string); + vlr_subscr_set_imei(vsub, mi->imei); break; case GSM_MI_TYPE_IMEISV: - vlr_subscr_set_imeisv(vsub, mi_string); + vlr_subscr_set_imeisv(vsub, mi->imeisv); break; + default: + return -EINVAL; } if (vsub->auth_fsm) { - switch (mi_type) { + switch (mi->type) { case GSM_MI_TYPE_IMSI: - osmo_fsm_inst_dispatch(vsub->auth_fsm, - VLR_AUTH_E_MS_ID_IMSI, mi_string); + return osmo_fsm_inst_dispatch(vsub->auth_fsm, + VLR_AUTH_E_MS_ID_IMSI, (void*)mi->imsi); break; } } if (vsub->lu_fsm) { - uint32_t event = 0; - switch (mi_type) { + switch (mi->type) { case GSM_MI_TYPE_IMSI: - event = VLR_ULA_E_ID_IMSI; - break; + return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_ID_IMSI, (void*)mi->imsi); case GSM_MI_TYPE_IMEI: - event = VLR_ULA_E_ID_IMEI; - break; + return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_ID_IMEI, (void*)mi->imei); case GSM_MI_TYPE_IMEISV: - event = VLR_ULA_E_ID_IMEISV; - break; + return osmo_fsm_inst_dispatch(vsub->lu_fsm, VLR_ULA_E_ID_IMEISV, (void*)mi->imeisv); default: - OSMO_ASSERT(0); - break; + return -EINVAL; } - osmo_fsm_inst_dispatch(vsub->lu_fsm, event, mi_string); } return 0; @@ -1212,8 +1466,7 @@ bool vlr_subscr_expire(struct vlr_subscr *vsub) return false; } -/* See TS 23.012 version 9.10.0 4.3.2.1 "Process Detach_IMSI_VLR" */ -int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub) +static int vlr_subscr_detach(struct vlr_subscr *vsub) { /* paranoia: should any LU or PARQ FSMs still be running, stop them. */ vlr_subscr_cancel_attach_fsm(vsub, OSMO_FSM_TERM_ERROR, GSM48_REJECT_CONGESTION); @@ -1229,6 +1482,13 @@ int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub) return 0; } +/* See TS 23.012 version 9.10.0 4.3.2.1 "Process Detach_IMSI_VLR" */ +int vlr_subscr_rx_imsi_detach(struct vlr_subscr *vsub) +{ + vlr_rate_ctr_inc(vsub->vlr, VLR_CTR_DETACH_BY_REQ); + return vlr_subscr_detach(vsub); +} + /* Tear down any running FSMs due to MSC connection timeout. * Visit all vsub->*_fsm pointers and give them a queue to send a final reject * message before the entire connection is torn down. @@ -1264,6 +1524,19 @@ struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops) /* defaults */ vlr->cfg.assign_tmsi = true; + vlr->cfg.nri_bitlen = OSMO_NRI_BITLEN_DEFAULT; + vlr->cfg.nri_ranges = osmo_nri_ranges_alloc(vlr); + + vlr->statg = osmo_stat_item_group_alloc(vlr, &vlr_statg_desc, 0); + if (!vlr->statg) + goto err_free; + + vlr->ctrg = rate_ctr_group_alloc(vlr, &vlr_ctrg_desc, 0); + if (!vlr->ctrg) + goto err_statg; + + /* reset shared timer definitions */ + osmo_tdefs_reset(msc_tdefs_vlr); /* osmo_auth_fsm.c */ OSMO_ASSERT(osmo_fsm_register(&vlr_auth_fsm) == 0); @@ -1275,6 +1548,12 @@ struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops) vlr_sgs_fsm_init(); return vlr; + +err_statg: + osmo_stat_item_group_free(vlr->statg); +err_free: + talloc_free(vlr); + return NULL; } int vlr_start(struct vlr_instance *vlr, struct gsup_client_mux *gcm) @@ -1353,13 +1632,9 @@ void vlr_subscr_rx_ciph_res(struct vlr_subscr *vsub, enum vlr_ciph_result_cause int vlr_set_ciph_mode(struct vlr_instance *vlr, struct osmo_fsm_inst *fi, void *msc_conn_ref, - bool ciph_required, bool umts_aka, bool retrieve_imeisv) { - if (!ciph_required) - return 0; - LOGPFSML(fi, LOGL_DEBUG, "Set Ciphering Mode\n"); return vlr->ops.set_ciph_mode(msc_conn_ref, umts_aka, retrieve_imeisv); } diff --git a/src/libvlr/vlr_access_req_fsm.c b/src/libvlr/vlr_access_req_fsm.c index 7684d02f0..629625ea4 100644 --- a/src/libvlr/vlr_access_req_fsm.c +++ b/src/libvlr/vlr_access_req_fsm.c @@ -40,6 +40,8 @@ static const struct value_string proc_arq_vlr_event_names[] = { OSMO_VALUE_STRING(PR_ARQ_E_START), OSMO_VALUE_STRING(PR_ARQ_E_ID_IMSI), OSMO_VALUE_STRING(PR_ARQ_E_AUTH_RES), + OSMO_VALUE_STRING(PR_ARQ_E_AUTH_NO_INFO), + OSMO_VALUE_STRING(PR_ARQ_E_AUTH_FAILURE), OSMO_VALUE_STRING(PR_ARQ_E_CIPH_RES), OSMO_VALUE_STRING(PR_ARQ_E_UPD_LOC_RES), OSMO_VALUE_STRING(PR_ARQ_E_TRACE_RES), @@ -67,7 +69,12 @@ struct proc_arq_priv { uint32_t tmsi; struct osmo_location_area_id lai; bool authentication_required; - bool ciphering_required; + /* is_ciphering_to_be_attempted: true when any A5/n > 0 are enabled. Ciphering is allowed, always attempt to get Auth Info from + * the HLR. */ + bool is_ciphering_to_be_attempted; + /* is_ciphering_required: true when A5/0 is disabled. If we cannot get Auth Info from the HLR, reject the + * subscriber. */ + bool is_ciphering_required; uint8_t key_seq; bool is_r99; bool is_utran; @@ -246,16 +253,13 @@ static void _proc_arq_vlr_node2_post_ciph(struct osmo_fsm_inst *fi) { struct proc_arq_priv *par = fi->priv; struct vlr_subscr *vsub = par->vsub; + int rc; LOGPFSM(fi, "%s()\n", __func__); - if (par->is_utran) { - int rc; - rc = par->vlr->ops.tx_common_id(par->msc_conn_ref); - if (rc) - LOGPFSML(fi, LOGL_ERROR, - "Error while sending Common ID (%d)\n", rc); - } + rc = par->vlr->ops.tx_common_id(par->msc_conn_ref); + if (rc) + LOGPFSML(fi, LOGL_ERROR, "Error while sending Common ID (%d)\n", rc); vsub->conf_by_radio_contact_ind = true; if (vsub->loc_conf_in_hlr_ind == false) { @@ -270,9 +274,12 @@ static void _proc_arq_vlr_node2_post_ciph(struct osmo_fsm_inst *fi) _proc_arq_vlr_node2_post_vlr(fi); } -static bool is_ciph_required(struct proc_arq_priv *par) +/* Return true when CipherModeCmd / SecurityModeCmd should be attempted. */ +static bool is_cmc_smc_to_be_attempted(struct proc_arq_priv *par) { - return par->ciphering_required; + /* UTRAN: always send SecModeCmd, even if ciphering is not required. + * GERAN: avoid sending CiphModeCmd if ciphering is not required. */ + return par->is_utran || par->is_ciphering_to_be_attempted; } static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi) @@ -283,7 +290,10 @@ static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi) LOGPFSM(fi, "%s()\n", __func__); - if (!is_ciph_required(par)) { + /* Continue with ciphering, if enabled. + * If auth/ciph is optional and the HLR returned no auth info, continue without ciphering. */ + if (!is_cmc_smc_to_be_attempted(par) + || (vsub->sec_ctx == VLR_SEC_CTX_NONE && !par->is_ciphering_required)) { _proc_arq_vlr_node2_post_ciph(fi); return; } @@ -302,7 +312,6 @@ static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi) } if (vlr_set_ciph_mode(vsub->vlr, fi, par->msc_conn_ref, - par->ciphering_required, umts_aka, vsub->vlr->cfg.retrieve_imeisv_ciphered)) { LOGPFSML(fi, LOGL_ERROR, @@ -315,13 +324,13 @@ static void _proc_arq_vlr_node2(struct osmo_fsm_inst *fi) osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_CIPH, 0, 0); } -static bool is_auth_required(struct proc_arq_priv *par) +static bool is_auth_to_be_attempted(struct proc_arq_priv *par) { /* The cases where the authentication procedure should be used * are defined in 3GPP TS 33.102 */ /* For now we use a default value passed in to vlr_lu_fsm(). */ return par->authentication_required || - (par->ciphering_required && !auth_try_reuse_tuple(par->vsub, par->key_seq)); + (par->is_ciphering_to_be_attempted && !auth_try_reuse_tuple(par->vsub, par->key_seq)); } /* after the IMSI is known */ @@ -335,11 +344,13 @@ static void proc_arq_vlr_fn_post_imsi(struct osmo_fsm_inst *fi) OSMO_ASSERT(vsub); /* TODO: Identity IMEI -> System Failure */ - if (is_auth_required(par)) { + if (is_auth_to_be_attempted(par)) { osmo_fsm_inst_state_chg(fi, PR_ARQ_S_WAIT_AUTH, 0, 0); - vsub->auth_fsm = auth_fsm_start(vsub, fi->log_level, fi, + vsub->auth_fsm = auth_fsm_start(vsub, fi, PR_ARQ_E_AUTH_RES, + PR_ARQ_E_AUTH_NO_INFO, + PR_ARQ_E_AUTH_FAILURE, par->is_r99, par->is_utran); } else { @@ -432,17 +443,34 @@ static void proc_arq_vlr_fn_w_obt_imsi(struct osmo_fsm_inst *fi, static void proc_arq_vlr_fn_w_auth(struct osmo_fsm_inst *fi, uint32_t event, void *data) { + struct proc_arq_priv *par = fi->priv; enum gsm48_reject_value *cause = data; - OSMO_ASSERT(event == PR_ARQ_E_AUTH_RES); + switch (event) { + case PR_ARQ_E_AUTH_RES: + /* Node 2 */ + _proc_arq_vlr_node2(fi); + return; - if (!cause || *cause) { - proc_arq_fsm_done(fi, cause? *cause : GSM48_REJECT_NETWORK_FAILURE); + case PR_ARQ_E_AUTH_FAILURE: + proc_arq_fsm_done(fi, cause ? *cause : GSM48_REJECT_NETWORK_FAILURE); return; - } - /* Node 2 */ - _proc_arq_vlr_node2(fi); + case PR_ARQ_E_AUTH_NO_INFO: + /* HLR returned no auth info for the subscriber. Continue only if authentication is optional. */ + if (par->authentication_required) { + proc_arq_fsm_done(fi, cause ? *cause : GSM48_REJECT_NETWORK_FAILURE); + return; + } + LOGPFSML(fi, LOGL_INFO, + "Attaching subscriber without auth (auth is optional, and no auth info received from HLR)\n"); + /* Node 2 */ + _proc_arq_vlr_node2(fi); + return; + + default: + OSMO_ASSERT(false); + } } static void proc_arq_vlr_fn_w_ciph(struct osmo_fsm_inst *fi, @@ -547,7 +575,9 @@ static const struct osmo_fsm_state proc_arq_vlr_states[] = { }, [PR_ARQ_S_WAIT_AUTH] = { .name = OSMO_STRINGIFY(PR_ARQ_S_WAIT_AUTH), - .in_event_mask = S(PR_ARQ_E_AUTH_RES), + .in_event_mask = S(PR_ARQ_E_AUTH_RES) | + S(PR_ARQ_E_AUTH_NO_INFO) | + S(PR_ARQ_E_AUTH_FAILURE), .out_state_mask = S(PR_ARQ_S_DONE) | S(PR_ARQ_S_WAIT_CIPH) | S(PR_ARQ_S_WAIT_UPD_LOC_CHILD) | @@ -632,17 +662,19 @@ vlr_proc_acc_req(struct osmo_fsm_inst *parent, void *parent_event_data, struct vlr_instance *vlr, void *msc_conn_ref, enum vlr_parq_type type, enum osmo_cm_service_type cm_service_type, - const uint8_t *mi_lv, + const struct osmo_mobile_identity *mi, const struct osmo_location_area_id *lai, bool authentication_required, - bool ciphering_required, + bool is_ciphering_to_be_attempted, + bool is_ciphering_required, uint8_t key_seq, bool is_r99, bool is_utran) { struct osmo_fsm_inst *fi; struct proc_arq_priv *par; - char mi_string[GSM48_MI_SIZE]; - uint8_t mi_type; + + if (is_ciphering_required) + OSMO_ASSERT(is_ciphering_to_be_attempted); fi = osmo_fsm_inst_alloc_child(&proc_arq_vlr_fsm, parent, parent_event_failure); @@ -660,7 +692,8 @@ vlr_proc_acc_req(struct osmo_fsm_inst *parent, par->parent_event_failure = parent_event_failure; par->parent_event_data = parent_event_data; par->authentication_required = authentication_required; - par->ciphering_required = ciphering_required; + par->is_ciphering_to_be_attempted = is_ciphering_to_be_attempted; + par->is_ciphering_required = is_ciphering_required; par->key_seq = key_seq; par->is_r99 = is_r99; par->is_utran = is_utran; @@ -668,26 +701,24 @@ vlr_proc_acc_req(struct osmo_fsm_inst *parent, LOGPFSM(fi, "rev=%s net=%s%s%s\n", is_r99 ? "R99" : "GSM", is_utran ? "UTRAN" : "GERAN", - (authentication_required || ciphering_required)? + (authentication_required || is_ciphering_to_be_attempted) ? " Auth" : " (no Auth)", - (authentication_required || ciphering_required)? - (ciphering_required? "+Ciph" : " (no Ciph)") + (authentication_required || is_ciphering_to_be_attempted) ? + (is_ciphering_to_be_attempted ? "+Ciph" : " (no Ciph)") : ""); if (is_utran && !authentication_required) LOGPFSML(fi, LOGL_ERROR, "Authentication off on UTRAN network. Good luck.\n"); - gsm48_mi_to_string(mi_string, sizeof(mi_string), mi_lv+1, mi_lv[0]); - mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; - switch (mi_type) { + switch (mi->type) { case GSM_MI_TYPE_IMSI: - osmo_strlcpy(par->imsi, mi_string, sizeof(par->imsi)); + OSMO_STRLCPY_ARRAY(par->imsi, mi->imsi); par->by_tmsi = false; break; case GSM_MI_TYPE_TMSI: par->by_tmsi = true; - par->tmsi = osmo_load32be(mi_lv+2); + par->tmsi = mi->tmsi; break; case GSM_MI_TYPE_IMEI: /* TODO: IMEI (emergency call) */ diff --git a/src/libvlr/vlr_auth_fsm.c b/src/libvlr/vlr_auth_fsm.c index 60265104d..b5052b072 100644 --- a/src/libvlr/vlr_auth_fsm.c +++ b/src/libvlr/vlr_auth_fsm.c @@ -1,4 +1,4 @@ -/* Osmocom Visitor Location Register (VLR) Autentication FSM */ +/* Osmocom Visitor Location Register (VLR) Authentication FSM */ /* (C) 2016 by Harald Welte <laforge@gnumonks.org> * @@ -51,6 +51,10 @@ struct auth_fsm_priv { bool auth_requested; int auth_tuple_max_reuse_count; /* see vlr->cfg instead */ + + uint32_t parent_event_success; + uint32_t parent_event_no_auth_info; + uint32_t parent_event_failure; }; /*********************************************************************** @@ -230,27 +234,50 @@ static void auth_fsm_onenter_failed(struct osmo_fsm_inst *fi, uint32_t prev_stat } } -static const char *vlr_auth_fsm_result_name(enum gsm48_reject_value result) -{ - if (!result) - return "PASSED"; - return get_value_string(gsm48_gmm_cause_names, result); -} +enum auth_fsm_result { + /* Authentication verified the subscriber. */ + AUTH_FSM_PASSED = 0, + /* HLR does not have authentication info for this subscriber. */ + AUTH_FSM_NO_AUTH_INFO, + /* Authentication was attempted but failed. */ + AUTH_FSM_FAILURE, +}; + +const char *auth_fsm_result_str[] = { + [AUTH_FSM_PASSED] = "PASSED", + [AUTH_FSM_NO_AUTH_INFO] = "NO_AUTH_INFO", + [AUTH_FSM_FAILURE] = "FAILURE", +}; /* Terminate the Auth FSM Instance and notify parent */ -static void auth_fsm_term(struct osmo_fsm_inst *fi, enum gsm48_reject_value result) +static void auth_fsm_term(struct osmo_fsm_inst *fi, enum auth_fsm_result result, enum gsm48_reject_value cause) { - LOGPFSM(fi, "Authentication terminating with result %s\n", - vlr_auth_fsm_result_name(result)); + struct auth_fsm_priv *afp = fi->priv; + + LOGPFSM(fi, "Authentication terminating with result %s%s%s\n", + auth_fsm_result_str[result], + cause ? ", cause " : "", + cause ? gsm48_reject_value_name(cause) : ""); - /* Do one final state transition (mostly for logging purpose) */ - if (!result) + /* Do one final state transition (mostly for logging purpose) + * and set the parent_term_event according to result */ + switch (result) { + case AUTH_FSM_PASSED: osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTHENTICATED, 0, 0); - else + fi->proc.parent_term_event = afp->parent_event_success; + break; + case AUTH_FSM_NO_AUTH_INFO: + osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0); + fi->proc.parent_term_event = afp->parent_event_no_auth_info; + break; + case AUTH_FSM_FAILURE: osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_AUTH_FAILED, 0, 0); + fi->proc.parent_term_event = afp->parent_event_failure; + break; + } /* return the result to the parent FSM */ - osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &result); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, &cause); } static void auth_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) @@ -275,7 +302,7 @@ static int _vlr_subscr_authenticate(struct osmo_fsm_inst *fi) LOGPFSML(fi, LOGL_ERROR, "A previous check ensured that an" " auth tuple was available, but now there is in fact" " none.\n"); - auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE); + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_NETWORK_FAILURE); return -1; } @@ -331,6 +358,7 @@ static void auth_fsm_wait_ai(struct osmo_fsm_inst *fi, uint32_t event, struct auth_fsm_priv *afp = fi->priv; struct vlr_subscr *vsub = afp->vsub; struct osmo_gsup_message *gsup = data; + enum gsm48_reject_value gsm48_rej; if (event == VLR_AUTH_E_HLR_SAI_NACK) LOGPFSM(fi, "GSUP: rx Auth Info Error cause: %d: %s\n", @@ -350,21 +378,25 @@ static void auth_fsm_wait_ai(struct osmo_fsm_inst *fi, uint32_t event, afp->auth_tuple_max_reuse_count = -1; goto pass; } - /* result = procedure error */ - auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE); - return; } switch (event) { case VLR_AUTH_E_HLR_SAI_ACK: + if (!gsup->num_auth_vectors) { + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_NETWORK_FAILURE); + return; + } vlr_subscr_update_tuples(vsub, gsup); goto pass; - break; case VLR_AUTH_E_HLR_SAI_NACK: - auth_fsm_term(fi, - gsup->cause == GMM_CAUSE_IMSI_UNKNOWN? - GSM48_REJECT_IMSI_UNKNOWN_IN_HLR - : GSM48_REJECT_NETWORK_FAILURE); + /* HLR did not return Auth Info, hence cannot authenticate. (The caller may still decide to permit + * attaching without authentication) */ + vlr_gmm_cause_to_mm_cause(gsup->cause, &gsm48_rej); + auth_fsm_term(fi, AUTH_FSM_NO_AUTH_INFO, gsm48_rej); + break; + case VLR_AUTH_E_HLR_SAI_ABORT: + vlr_gmm_cause_to_mm_cause(gsup->cause, &gsm48_rej); + auth_fsm_term(fi, AUTH_FSM_FAILURE, gsm48_rej); break; } @@ -397,10 +429,10 @@ static void auth_fsm_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event, VLR_SUB_AS_WAIT_ID_IMSI, vlr_timer(vlr, 3270), 3270); } else { - auth_fsm_term(fi, GSM48_REJECT_ILLEGAL_MS); + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_ILLEGAL_MS); } } else { - auth_fsm_term(fi, 0); + auth_fsm_term(fi, AUTH_FSM_PASSED, 0); } break; case VLR_AUTH_E_MS_AUTH_FAIL: @@ -412,7 +444,7 @@ static void auth_fsm_wait_auth_resp(struct osmo_fsm_inst *fi, uint32_t event, VLR_SUB_AS_NEEDS_AUTH_WAIT_SAI_RESYNC, GSM_29002_TIMER_M, 0); } else - auth_fsm_term(fi, GSM48_REJECT_ILLEGAL_MS); + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_ILLEGAL_MS); break; } } @@ -432,26 +464,23 @@ static void auth_fsm_wait_ai_resync(struct osmo_fsm_inst *fi, gsup->cause != GMM_CAUSE_IMSI_UNKNOWN) || (event == VLR_AUTH_E_HLR_SAI_ABORT)) { /* result = procedure error */ - auth_fsm_term(fi, GSM48_REJECT_NETWORK_FAILURE); + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_NETWORK_FAILURE); } switch (event) { case VLR_AUTH_E_HLR_SAI_ACK: vlr_subscr_update_tuples(vsub, gsup); - goto pass; + osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP_RESYNC, + vlr_timer(vsub->vlr, 3260), 3260); + _vlr_subscr_authenticate(fi); break; case VLR_AUTH_E_HLR_SAI_NACK: auth_fsm_term(fi, + AUTH_FSM_FAILURE, gsup->cause == GMM_CAUSE_IMSI_UNKNOWN? GSM48_REJECT_IMSI_UNKNOWN_IN_HLR : GSM48_REJECT_NETWORK_FAILURE); break; } - - return; -pass: - osmo_fsm_inst_state_chg(fi, VLR_SUB_AS_WAIT_RESP_RESYNC, - vlr_timer(vsub->vlr, 3260), 3260); - _vlr_subscr_authenticate(fi); } /* Waiting for AUTH RESP from MS (re-sync case) */ @@ -477,16 +506,16 @@ static void auth_fsm_wait_auth_resp_resync(struct osmo_fsm_inst *fi, vlr_timer(vlr, 3270), 3270); } else { /* Result = Aborted */ - auth_fsm_term(fi, GSM48_REJECT_SYNCH_FAILURE); + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_SYNCH_FAILURE); } } else { /* Result = Pass */ - auth_fsm_term(fi, 0); + auth_fsm_term(fi, AUTH_FSM_PASSED, 0); } break; case VLR_AUTH_E_MS_AUTH_FAIL: /* Second failure: Result = Fail */ - auth_fsm_term(fi, GSM48_REJECT_SYNCH_FAILURE); + auth_fsm_term(fi, AUTH_FSM_FAILURE, GSM48_REJECT_SYNCH_FAILURE); break; } } @@ -595,25 +624,25 @@ struct osmo_fsm vlr_auth_fsm = { /* MSC->VLR: Start Procedure Authenticate_VLR (TS 23.012 Ch. 4.1.2.2) */ struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub, - uint32_t log_level, struct osmo_fsm_inst *parent, - uint32_t parent_term_event, + uint32_t parent_event_success, + uint32_t parent_event_no_auth_info, + uint32_t parent_event_failure, bool is_r99, bool is_utran) { struct osmo_fsm_inst *fi; struct auth_fsm_priv *afp; - fi = osmo_fsm_inst_alloc_child(&vlr_auth_fsm, parent, - parent_term_event); + fi = osmo_fsm_inst_alloc_child(&vlr_auth_fsm, parent, parent_event_failure); if (!fi) { - osmo_fsm_inst_dispatch(parent, parent_term_event, 0); + osmo_fsm_inst_dispatch(parent, parent_event_failure, 0); return NULL; } afp = talloc_zero(fi, struct auth_fsm_priv); if (!afp) { - osmo_fsm_inst_dispatch(parent, parent_term_event, 0); + osmo_fsm_inst_dispatch(parent, parent_event_failure, 0); return NULL; } @@ -622,6 +651,9 @@ struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub, afp->by_imsi = true; afp->is_r99 = is_r99; afp->is_utran = is_utran; + afp->parent_event_success = parent_event_success; + afp->parent_event_no_auth_info = parent_event_no_auth_info; + afp->parent_event_failure = parent_event_failure; fi->priv = afp; vsub->auth_fsm = fi; diff --git a/src/libvlr/vlr_auth_fsm.h b/src/libvlr/vlr_auth_fsm.h index 1f2cb4969..828384206 100644 --- a/src/libvlr/vlr_auth_fsm.h +++ b/src/libvlr/vlr_auth_fsm.h @@ -27,12 +27,13 @@ enum vlr_fsm_auth_event { VLR_AUTH_E_MS_ID_IMSI, }; -struct osmo_fsm vlr_auth_fsm; +extern struct osmo_fsm vlr_auth_fsm; struct osmo_fsm_inst *auth_fsm_start(struct vlr_subscr *vsub, - uint32_t log_level, struct osmo_fsm_inst *parent, - uint32_t parent_term_event, + uint32_t parent_event_success, + uint32_t parent_event_no_auth_info, + uint32_t parent_event_failure, bool is_r99, bool is_utran); diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c index 2db571134..5d8f78bc2 100644 --- a/src/libvlr/vlr_lu_fsm.c +++ b/src/libvlr/vlr_lu_fsm.c @@ -469,6 +469,8 @@ static void lu_compl_vlr_wait_subscr_pres(struct osmo_fsm_inst *fi, lu_compl_vlr_new_tmsi(fi); return; } + /* else, any previously used TMSI is now invalid. */ + vsub->tmsi = GSM_RESERVED_TMSI; /* Location Updating Accept */ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, GSM_RESERVED_TMSI); @@ -514,6 +516,8 @@ static void lu_compl_vlr_wait_imei(struct osmo_fsm_inst *fi, uint32_t event, /* Wait for TMSI ack */ return; } + /* else, any previously used TMSI is now invalid. */ + vsub->tmsi = GSM_RESERVED_TMSI; /* No TMSI needed, accept now. */ vlr->ops.tx_lu_acc(lcvp->msc_conn_ref, GSM_RESERVED_TMSI); @@ -642,7 +646,9 @@ static const struct value_string fsm_lu_event_names[] = { OSMO_VALUE_STRING(VLR_ULA_E_UPDATE_LA), OSMO_VALUE_STRING(VLR_ULA_E_SEND_ID_ACK), OSMO_VALUE_STRING(VLR_ULA_E_SEND_ID_NACK), - OSMO_VALUE_STRING(VLR_ULA_E_AUTH_RES), + OSMO_VALUE_STRING(VLR_ULA_E_AUTH_SUCCESS), + OSMO_VALUE_STRING(VLR_ULA_E_AUTH_NO_INFO), + OSMO_VALUE_STRING(VLR_ULA_E_AUTH_FAILURE), OSMO_VALUE_STRING(VLR_ULA_E_CIPH_RES), OSMO_VALUE_STRING(VLR_ULA_E_ID_IMSI), OSMO_VALUE_STRING(VLR_ULA_E_ID_IMEI), @@ -676,7 +682,12 @@ struct lu_fsm_priv { struct osmo_location_area_id old_lai; struct osmo_location_area_id new_lai; bool authentication_required; - bool ciphering_required; + /* is_ciphering_to_be_attempted: true when any A5/n > 0 are enabled. Ciphering is allowed, always attempt to get Auth Info from + * the HLR. */ + bool is_ciphering_to_be_attempted; + /* is_ciphering_required: true when A5/0 is disabled. If we cannot get Auth Info from the HLR, reject the + * subscriber. */ + bool is_ciphering_required; uint8_t key_seq; bool is_r99; bool is_utran; @@ -688,24 +699,26 @@ struct lu_fsm_priv { static bool lai_in_this_vlr(struct vlr_instance *vlr, const struct osmo_location_area_id *lai) { - /* TODO: VLR needs to keep a locally configued list of LAIs */ + /* TODO: VLR needs to keep a locally configured list of LAIs */ return true; } -/* Determine if authentication is required */ -static bool is_auth_required(struct lu_fsm_priv *lfp) +/* Return true when authentication should be attempted. */ +static bool try_auth(struct lu_fsm_priv *lfp) { /* The cases where the authentication procedure should be used * are defined in 3GPP TS 33.102 */ /* For now we use a default value passed in to vlr_lu_fsm(). */ return lfp->authentication_required || - (lfp->ciphering_required && !auth_try_reuse_tuple(lfp->vsub, lfp->key_seq)); + (lfp->is_ciphering_to_be_attempted && !auth_try_reuse_tuple(lfp->vsub, lfp->key_seq)); } -/* Determine if ciphering is required */ -static bool is_ciph_required(struct lu_fsm_priv *lfp) +/* Return true when CipherModeCmd / SecurityModeCmd should be attempted. */ +static bool is_cmc_smc_to_be_attempted(struct lu_fsm_priv *lfp) { - return lfp->ciphering_required; + /* UTRAN: always send SecModeCmd, even if ciphering is not required. + * GERAN: avoid sending CiphModeCmd if ciphering is not required. */ + return lfp->is_utran || lfp->is_ciphering_to_be_attempted; } /* Determine if a HLR Update is required */ @@ -818,18 +831,15 @@ static void vlr_loc_upd_post_ciph(struct osmo_fsm_inst *fi) { struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi); struct vlr_subscr *vsub = lfp->vsub; + int rc; LOGPFSM(fi, "%s()\n", __func__); OSMO_ASSERT(vsub); - if (lfp->is_utran) { - int rc; - rc = lfp->vlr->ops.tx_common_id(lfp->msc_conn_ref); - if (rc) - LOGPFSML(fi, LOGL_ERROR, - "Error while sending Common ID (%d)\n", rc); - } + rc = lfp->vlr->ops.tx_common_id(lfp->msc_conn_ref); + if (rc) + LOGPFSML(fi, LOGL_ERROR, "Error while sending Common ID (%d)\n", rc); vsub->conf_by_radio_contact_ind = true; /* Update LAI */ @@ -856,7 +866,10 @@ static void vlr_loc_upd_post_auth(struct osmo_fsm_inst *fi) OSMO_ASSERT(vsub); - if (!is_ciph_required(lfp)) { + /* Continue with ciphering, if enabled. + * If auth/ciph is optional and the HLR returned no auth info, continue without ciphering. */ + if (!is_cmc_smc_to_be_attempted(lfp) + || (vsub->sec_ctx == VLR_SEC_CTX_NONE && !lfp->is_ciphering_required)) { vlr_loc_upd_post_ciph(fi); return; } @@ -881,7 +894,6 @@ static void vlr_loc_upd_post_auth(struct osmo_fsm_inst *fi) } if (vlr_set_ciph_mode(vsub->vlr, fi, lfp->msc_conn_ref, - lfp->ciphering_required, umts_aka, vsub->vlr->cfg.retrieve_imeisv_ciphered)) { LOGPFSML(fi, LOGL_ERROR, @@ -902,12 +914,15 @@ static void vlr_loc_upd_node1(struct osmo_fsm_inst *fi) OSMO_ASSERT(vsub); - if (is_auth_required(lfp)) { + if (try_auth(lfp)) { /* Authenticate_VLR */ osmo_fsm_inst_state_chg(fi, VLR_ULA_S_WAIT_AUTH, LU_TIMEOUT_LONG, 0); - vsub->auth_fsm = auth_fsm_start(lfp->vsub, fi->log_level, - fi, VLR_ULA_E_AUTH_RES, + vsub->auth_fsm = auth_fsm_start(lfp->vsub, + fi, + VLR_ULA_E_AUTH_SUCCESS, + VLR_ULA_E_AUTH_NO_INFO, + VLR_ULA_E_AUTH_FAILURE, lfp->is_r99, lfp->is_utran); } else { @@ -1140,17 +1155,32 @@ static void lu_fsm_wait_auth(struct osmo_fsm_inst *fi, uint32_t event, struct lu_fsm_priv *lfp = lu_fsm_fi_priv(fi); enum gsm48_reject_value *res = data; - OSMO_ASSERT(event == VLR_ULA_E_AUTH_RES); - lfp->upd_hlr_vlr_fsm = NULL; - if (!res || *res) { - lu_fsm_failure(fi, res? *res : GSM48_REJECT_NETWORK_FAILURE); + switch (event) { + case VLR_ULA_E_AUTH_SUCCESS: + /* Result == Pass */ + vlr_loc_upd_post_auth(fi); + return; + + case VLR_ULA_E_AUTH_FAILURE: + lu_fsm_failure(fi, res ? *res : GSM48_REJECT_NETWORK_FAILURE); return; - } - /* Result == Pass */ - vlr_loc_upd_post_auth(fi); + case VLR_ULA_E_AUTH_NO_INFO: + /* HLR returned no auth info for the subscriber. Continue only if authentication is optional. */ + if (lfp->authentication_required || lfp->is_ciphering_required) { + lu_fsm_failure(fi, res ? *res : GSM48_REJECT_NETWORK_FAILURE); + return; + } + LOGPFSML(fi, LOGL_INFO, + "Attaching subscriber without auth (auth is optional, and no auth info received from HLR)\n"); + vlr_loc_upd_post_auth(fi); + return; + + default: + OSMO_ASSERT(false); + } } static void lu_fsm_wait_ciph(struct osmo_fsm_inst *fi, uint32_t event, @@ -1365,7 +1395,9 @@ static const struct osmo_fsm_state vlr_lu_fsm_states[] = { .action = lu_fsm_wait_pvlr, }, [VLR_ULA_S_WAIT_AUTH] = { - .in_event_mask = S(VLR_ULA_E_AUTH_RES), + .in_event_mask = S(VLR_ULA_E_AUTH_SUCCESS) | + S(VLR_ULA_E_AUTH_NO_INFO) | + S(VLR_ULA_E_AUTH_FAILURE), .out_state_mask = S(VLR_ULA_S_WAIT_CIPH) | S(VLR_ULA_S_WAIT_LU_COMPL) | S(VLR_ULA_S_WAIT_HLR_UPD) | @@ -1477,7 +1509,8 @@ vlr_loc_update(struct osmo_fsm_inst *parent, const struct osmo_location_area_id *old_lai, const struct osmo_location_area_id *new_lai, bool authentication_required, - bool ciphering_required, + bool is_ciphering_to_be_attempted, + bool is_ciphering_required, uint8_t key_seq, bool is_r99, bool is_utran, bool assign_tmsi) @@ -1485,6 +1518,9 @@ vlr_loc_update(struct osmo_fsm_inst *parent, struct osmo_fsm_inst *fi; struct lu_fsm_priv *lfp; + if (is_ciphering_required) + OSMO_ASSERT(is_ciphering_to_be_attempted); + fi = osmo_fsm_inst_alloc_child(&vlr_lu_fsm, parent, parent_event_failure); if (!fi) return NULL; @@ -1501,7 +1537,8 @@ vlr_loc_update(struct osmo_fsm_inst *parent, lfp->parent_event_failure = parent_event_failure; lfp->parent_event_data = parent_event_data; lfp->authentication_required = authentication_required; - lfp->ciphering_required = ciphering_required; + lfp->is_ciphering_to_be_attempted = is_ciphering_to_be_attempted; + lfp->is_ciphering_required = is_ciphering_required; lfp->key_seq = key_seq; lfp->is_r99 = is_r99; lfp->is_utran = is_utran; @@ -1516,10 +1553,10 @@ vlr_loc_update(struct osmo_fsm_inst *parent, LOGPFSM(fi, "rev=%s net=%s%s%s\n", is_r99 ? "R99" : "GSM", is_utran ? "UTRAN" : "GERAN", - (authentication_required || ciphering_required)? + (authentication_required || is_ciphering_to_be_attempted) ? " Auth" : " (no Auth)", - (authentication_required || ciphering_required)? - (ciphering_required? "+Ciph" : " (no Ciph)") + (authentication_required || is_ciphering_to_be_attempted) ? + (is_ciphering_to_be_attempted ? "+Ciph" : " (no Ciph)") : ""); if (is_utran && !authentication_required) diff --git a/src/libvlr/vlr_sgs.c b/src/libvlr/vlr_sgs.c index 452de2cca..61db585b6 100644 --- a/src/libvlr/vlr_sgs.c +++ b/src/libvlr/vlr_sgs.c @@ -44,7 +44,7 @@ const struct value_string sgs_state_counter_names[] = { }; /* Reset all SGs-Associations back to zero. - * \param[in] vlr VLR instace. */ + * \param[in] vlr VLR instance. */ void vlr_sgs_reset(struct vlr_instance *vlr) { struct vlr_subscr *vsub; @@ -59,20 +59,21 @@ void vlr_sgs_reset(struct vlr_instance *vlr) } /*! Perform an SGs location update. - * \param[in] vlr VLR instace. + * \param[in] vlr VLR instance. * \param[in] cfg SGs interface configuration parameters. - * \param[in] response_cb calback function that is called when LU is done. - * \param[in] paging_cb calback function that is called when LU needs to page. - * \param[in] mminfo_cb calback function that is called to provide MM info to the UE. + * \param[in] response_cb callback function that is called when LU is done. + * \param[in] paging_cb callback function that is called when LU needs to page. + * \param[in] mminfo_cb callback function that is called to provide MM info to the UE. * \param[in] mme_name fqdn of the requesting MME (mme-name). * \param[in] type location update type (normal or IMSI attach). * \param[in] imsi mobile identity (IMSI). * \param[in] new_lai identifier of the new location area. + * \param[in] last_eutran_plnm_id Last E-UTRAN PLMN ID (can be NULL). * \returns 0 in case of success, -EINVAL in case of error. */ int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg, vlr_sgs_lu_response_cb_t response_cb, vlr_sgs_lu_paging_cb_t paging_cb, vlr_sgs_lu_mminfo_cb_t mminfo_cb, char *mme_name, enum vlr_lu_type type, const char *imsi, - struct osmo_location_area_id *new_lai) + struct osmo_location_area_id *new_lai, struct osmo_plmn_id *last_eutran_plmn) { struct vlr_subscr *vsub = NULL; @@ -82,7 +83,7 @@ int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg, OSMO_ASSERT(cfg); OSMO_ASSERT(imsi); - vsub = vlr_subscr_find_or_create_by_imsi(vlr, imsi, VSUB_USE_SGS, NULL); + vsub = vlr_subscr_find_or_create_by_imsi(vlr, imsi, VSUB_USE_SGS_LU, NULL); if (!vsub) { LOGP(DSGS, LOGL_ERROR, "VLR subscriber allocation failed\n"); return -EINVAL; @@ -93,6 +94,7 @@ int vlr_sgs_loc_update(struct vlr_instance *vlr, struct vlr_sgs_cfg *cfg, vsub->sgs.paging_cb = paging_cb; vsub->sgs.mminfo_cb = mminfo_cb; vlr_subscr_set_imsi(vsub, imsi); + vlr_subscr_set_last_used_eutran_plmn_id(vsub, last_eutran_plmn); osmo_strlcpy(vsub->sgs.mme_name, mme_name, sizeof(vsub->sgs.mme_name)); osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_RX_LU_FROM_MME, NULL); @@ -117,6 +119,9 @@ void vlr_sgs_loc_update_acc_sent(struct vlr_subscr *vsub) { osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_LU_ACCEPT, NULL); + /* Balance vlr_subscr_find_or_create_by_imsi() in vlr_sgs_loc_update() */ + vlr_subscr_put(vsub, VSUB_USE_SGS_LU); + /* FIXME: At this point we need to check the status of Ts5 and if * it is still running this means the LU has interrupted the paging, * and we need to start paging again. 3GPP TS 29.118, @@ -128,6 +133,8 @@ void vlr_sgs_loc_update_acc_sent(struct vlr_subscr *vsub) void vlr_sgs_loc_update_rej_sent(struct vlr_subscr *vsub) { osmo_fsm_inst_dispatch(vsub->sgs_fsm, SGS_UE_E_TX_LU_REJECT, NULL); + /* Balance vlr_subscr_find_or_create_by_imsi() in vlr_sgs_loc_update() */ + vlr_subscr_put(vsub, VSUB_USE_SGS_LU); } /*! Perform an SGs IMSI detach. @@ -146,8 +153,10 @@ void vlr_sgs_imsi_detach(struct vlr_instance *vlr, const char *imsi, enum sgsap_ /* See also: 3GPP TS 29.118, 5.6.3 Procedures in the VLR: In case of * an implicit detach, we are supposed to check if the state of the * SGs-association, and only when it is not SGs-NULL, we may proceed. */ - if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && type == SGSAP_ID_NONEPS_T_IMPLICIT_UE_EPS_NONEPS) + if (vsub->sgs_fsm->state == SGS_UE_ST_NULL && type == SGSAP_ID_NONEPS_T_IMPLICIT_UE_EPS_NONEPS) { + vlr_subscr_put(vsub, __func__); return; + } switch (type) { case SGSAP_ID_NONEPS_T_EXPLICIT_UE_NONEPS: @@ -299,7 +308,7 @@ static void Ts5_timeout_cb(void *arg) { struct vlr_subscr *vsub = arg; - /* 3GPP TS 29.118 does not specify a specif action that has to happen + /* 3GPP TS 29.118 does not specify a specific action that has to happen * in case Ts5 times out. The timeout just indicates that the paging * failed. Other actions may check the status of Ts5 to see if a paging * is still ongoing or not. */ @@ -327,7 +336,7 @@ void vlr_sgs_pag(struct vlr_subscr *vsub, enum sgsap_service_ind serv_ind) /* Note: 3GPP TS 29.118, chapter 4.2.2 mentions paging in the FSM * diagram, but paging never causes a state transition except when * an explicit failure is indicated (MME actively rejects paging). - * Apparantly it is also possible that an LU happens while the paging + * Apparently it is also possible that an LU happens while the paging * is still ongoing and Ts5 is running. (chapter 5.1.2.3). This means * that the paging procedure is intended to run in parallel to the * SGs FSM and given that the benaviour around Ts5 must be implemented diff --git a/src/libvlr/vlr_sgs_fsm.c b/src/libvlr/vlr_sgs_fsm.c index 49ad09a59..2771cf5ce 100644 --- a/src/libvlr/vlr_sgs_fsm.c +++ b/src/libvlr/vlr_sgs_fsm.c @@ -54,11 +54,15 @@ static void to_null(struct osmo_fsm_inst *fi) struct vlr_subscr *vsub = fi->priv; osmo_fsm_inst_state_chg(fi, SGS_UE_ST_NULL, 0, 0); - /* Note: This is only relevent for cases where we are in the middle + /* Note: This is only relevant for cases where we are in the middle * of an TMSI reallocation procedure. Should a failure of some sort * put us to NULL state, we have to free the pending TMSI */ vsub->tmsi_new = GSM_RESERVED_TMSI; + /* Make sure we remove recorded Last EUTRAN PLMN Id when UE ceases to be + * available over SGs */ + vlr_subscr_set_last_used_eutran_plmn_id(vsub, NULL); + /* Make sure any ongoing paging is aborted. */ if (vsub->cs.is_paging) paging_expired(vsub); @@ -156,7 +160,7 @@ static void sgs_ue_fsm_lau_present(struct osmo_fsm_inst *fi, uint32_t event, voi /* Check if we expect a TMSI REALLOCATION COMPLETE message from the MME * by checking the tmsi_new flag. If this flag is not GSM_RESERVED_TMSI * we know that we have a TMSI pending and need to wait for the MME - * to acknowlege first */ + * to acknowledge first */ if (vsub->tmsi_new != GSM_RESERVED_TMSI) { osmo_fsm_inst_state_chg(fi, SGS_UE_ST_ASSOCIATED, vsub->sgs.cfg.timer[SGS_STATE_TS6_2], SGS_STATE_TS6_2); @@ -222,7 +226,7 @@ static void sgs_ue_fsm_associated(struct osmo_fsm_inst *fi, uint32_t event, void /* Note: We are already in SGS_UE_ST_ASSOCIATED but the * transition that lead us here had is guarded with Ts6-1, - * wo we change the state now once more without timeout + * so we change the state now once more without timeout * to ensure the timer is stopped */ osmo_fsm_inst_state_chg(fi, SGS_UE_ST_ASSOCIATED, 0, 0); break; @@ -234,6 +238,7 @@ static void sgs_ue_fsm_associated(struct osmo_fsm_inst *fi, uint32_t event, void if (*cause == SGSAP_SGS_CAUSE_MT_CSFB_REJ_USER) break; to_null(fi); + break; case SGS_UE_E_RX_ALERT_FAILURE: to_null(fi); break; @@ -355,7 +360,7 @@ static struct osmo_fsm sgs_ue_fsm = { .event_names = sgs_ue_fsm_event_names, }; -/*! Initalize/Register SGs FSM in osmo-fsm subsystem */ +/*! Initialize/Register SGs FSM in osmo-fsm subsystem */ void vlr_sgs_fsm_init(void) { if (osmo_fsm_find_by_name(sgs_ue_fsm.name) != &sgs_ue_fsm) diff --git a/src/osmo-msc/Makefile.am b/src/osmo-msc/Makefile.am index 7b56c7458..0380d5d1f 100644 --- a/src/osmo-msc/Makefile.am +++ b/src/osmo-msc/Makefile.am @@ -19,6 +19,7 @@ AM_CFLAGS = \ $(LIBOSMOSIGTRAN_CFLAGS) \ $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(LIBOSMOGSUPCLIENT_CFLAGS) \ + $(LIBSQLITE3_CFLAGS) \ $(NULL) AM_LDFLAGS = \ @@ -42,12 +43,20 @@ osmo_msc_LDADD = \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOABIS_LIBS) \ $(LIBOSMONETIF_LIBS) \ - $(LIBSMPP34_LIBS) \ $(LIBOSMORANAP_LIBS) \ $(LIBASN1C_LIBS) \ $(LIBOSMOSIGTRAN_LIBS) \ $(LIBOSMOMGCPCLIENT_LIBS) \ $(LIBOSMOGSUPCLIENT_LIBS) \ - -ldbi \ + $(LIBSQLITE3_LIBS) \ -lsctp \ $(NULL) + +if BUILD_SMPP + +osmo_msc_LDADD += \ + $(top_builddir)/src/libsmpputil/libsmpputil.a \ + $(LIBSMPP34_LIBS) \ + $(NULL) + +endif diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c index 3860589e7..913bd212f 100644 --- a/src/osmo-msc/msc_main.c +++ b/src/osmo-msc/msc_main.c @@ -1,4 +1,4 @@ -/* OsmoMSC - Circuit-Switched Core Network (MSC+VLR+HLR+SMSC) implementation +/* OsmoMSC - Circuit-Switched Core Network (MSC+VLR+SMSC) implementation */ /* (C) 2016-2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> @@ -26,6 +26,7 @@ #include <stdbool.h> #include <unistd.h> +#include <stdio.h> #include <time.h> #include <errno.h> #include <signal.h> @@ -52,13 +53,14 @@ #include <osmocom/vty/ports.h> #include <osmocom/vty/logging.h> #include <osmocom/vty/misc.h> +#include <osmocom/vty/cpu_sched_vty.h> #include <osmocom/msc/vty.h> #include <osmocom/msc/mncc.h> #include <osmocom/msc/rrlp.h> #include <osmocom/ctrl/control_if.h> #include <osmocom/ctrl/control_vty.h> #include <osmocom/ctrl/ports.h> -#include <osmocom/msc/smpp.h> +#include <osmocom/smpp/smpp.h> #include <osmocom/sigtran/osmo_ss7.h> #include <osmocom/mgcp_client/mgcp_client.h> #include <osmocom/msc/sgs_iface.h> @@ -98,12 +100,10 @@ void *tall_map_ctx = NULL; /* end deps from libbsc legacy. */ static struct { - const char *database_name; const char *config_file; int daemonize; const char *mncc_sock_path; } msc_cmdline_config = { - .database_name = "sms.db", .config_file = "osmo-msc.cfg", }; @@ -119,35 +119,65 @@ static void print_usage() static void print_help() { - printf(" Some useful help...\n"); + printf("Some useful options:\n"); printf(" -h --help This text.\n"); printf(" -d option --debug=DCC:DMM:DRR: Enable debugging.\n"); printf(" -D --daemonize Fork the process into a background daemon.\n"); printf(" -c --config-file filename The config file to use.\n"); printf(" -s --disable-color\n"); - printf(" -l --database db-name The database to use.\n"); printf(" -T --timestamp Prefix every log line with a timestamp.\n"); printf(" -V --version Print the version of OsmoMSC.\n"); printf(" -e --log-level number Set a global loglevel.\n"); - printf(" -M --mncc-sock-path PATH Disable built-in MNCC handler and offer socket.\n"); + + printf("\nVTY reference generation:\n"); + printf(" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"); + printf(" --vty-ref-xml Generate the VTY reference XML output and exit.\n"); +} + +static void handle_long_options(const char *prog_name, const int long_option) +{ + static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT; + + switch (long_option) { + case 1: + vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (vty_ref_mode < 0) { + fprintf(stderr, "%s: Unknown VTY reference generation " + "mode '%s'\n", prog_name, optarg); + exit(2); + } + break; + case 2: + fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", + get_value_string(vty_ref_gen_mode_names, vty_ref_mode), + get_value_string(vty_ref_gen_mode_desc, vty_ref_mode)); + vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode); + exit(0); + default: + fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); + exit(2); + } } static void handle_options(int argc, char **argv) { while (1) { int option_index = 0, c; + static int long_option = 0; static struct option long_options[] = { {"help", 0, 0, 'h'}, {"debug", 1, 0, 'd'}, {"daemonize", 0, 0, 'D'}, {"config-file", 1, 0, 'c'}, {"disable-color", 0, 0, 's'}, - {"database", 1, 0, 'l'}, + {"database", 1, 0, 'l'}, /* deprecated */ {"timestamp", 0, 0, 'T'}, {"version", 0, 0, 'V' }, {"log-level", 1, 0, 'e'}, - {"mncc-sock-path", 1, 0, 'M'}, + {"mncc-sock-path", 1, 0, 'M'}, /* deprecated */ {"no-dbcounter", 0, 0, 'C'}, /* deprecated */ + {"vty-ref-mode", 1, &long_option, 1}, + {"vty-ref-xml", 0, &long_option, 2}, {0, 0, 0, 0} }; @@ -161,6 +191,9 @@ static void handle_options(int argc, char **argv) print_usage(); print_help(); exit(0); + case 0: + handle_long_options(argv[0], long_option); + break; case 's': log_set_use_color(osmo_stderr_target, 0); break; @@ -171,7 +204,9 @@ static void handle_options(int argc, char **argv) msc_cmdline_config.daemonize = 1; break; case 'l': - msc_cmdline_config.database_name = optarg; + fprintf(stderr, "Command line argument '-%c' is deprecated, use VTY " + "parameter 'smsc' / 'database %s' instead.\n", c, optarg); + exit(2); break; case 'c': msc_cmdline_config.config_file = optarg; @@ -184,6 +219,8 @@ static void handle_options(int argc, char **argv) break; case 'M': msc_cmdline_config.mncc_sock_path = optarg; + fprintf(stderr, "Command line argument '-%c' is deprecated, use VTY " + "parameter 'msc' / 'mncc external %s' instead.\n", c, optarg); break; case 'C': fprintf(stderr, "-C is deprecated and does nothing."); @@ -198,10 +235,15 @@ static void handle_options(int argc, char **argv) exit(-1); } } + + if (argc > optind) { + fprintf(stderr, "Unsupported positional arguments on command line\n"); + exit(2); + } } -struct gsm_network *msc_network_alloc(void *ctx, - mncc_recv_cb_t mncc_recv) +static struct gsm_network *msc_network_alloc(void *ctx, + mncc_recv_cb_t mncc_recv) { struct gsm_network *net = gsm_network_init(ctx, mncc_recv); if (!net) @@ -214,9 +256,14 @@ struct gsm_network *msc_network_alloc(void *ctx, MSC_HLR_REMOTE_IP_DEFAULT); net->gsup_server_port = MSC_HLR_REMOTE_PORT_DEFAULT; - mgcp_client_conf_init(&net->mgw.conf); - net->mgw.tdefs = g_mgw_tdefs; + net->mgw.mgw_pool = mgcp_client_pool_alloc(net); + net->mgw.conf = mgcp_client_conf_alloc(net); net->call_waiting = true; + net->lcls_permitted = false; + + net->mgw.tdefs = g_mgw_tdefs; + osmo_tdefs_reset(net->mgw.tdefs); + net->sms_queue_cfg = sms_queue_cfg_alloc(ctx); return net; } @@ -229,20 +276,29 @@ void msc_network_shutdown(struct gsm_network *net) static struct gsm_network *msc_network = NULL; extern void *tall_vty_ctx; -static void signal_handler(int signal) +static void signal_handler(int signum) { - fprintf(stdout, "signal %u received\n", signal); + fprintf(stdout, "signal %u received\n", signum); - switch (signal) { + switch (signum) { case SIGINT: case SIGTERM: - LOGP(DMSC, LOGL_NOTICE, "Terminating due to signal %d\n", signal); + LOGP(DMSC, LOGL_NOTICE, "Terminating due to signal %d\n", signum); quit++; break; case SIGABRT: osmo_generate_backtrace(); - /* in case of abort, we want to obtain a talloc report - * and then return to the caller, who will abort the process */ + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_msc_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: talloc_report(tall_vty_ctx, stderr); talloc_report_full(tall_msc_ctx, stderr); @@ -262,6 +318,11 @@ static int msc_vty_go_parent(struct vty *vty) vty->node = CONFIG_NODE; vty->index = NULL; break; + case MGW_NODE: + OSMO_ASSERT(msc_network != NULL); + vty->node = GSMNET_NODE; + vty->index = msc_network; + break; case SMPP_ESME_NODE: vty->node = SMPP_NODE; vty->index = NULL; @@ -269,9 +330,19 @@ static int msc_vty_go_parent(struct vty *vty) case SMPP_NODE: case MSC_NODE: case MNCC_INT_NODE: + case ASCI_NODE: vty->node = CONFIG_NODE; vty->index = NULL; break; + case GCR_NODE: + vty->node = ASCI_NODE; + vty->index = NULL; + break; + case VGC_NODE: + case VBC_NODE: + vty->node = GCR_NODE; + vty->index = NULL; + break; case SUBSCR_NODE: vty->node = ENABLE_NODE; vty->index = NULL; @@ -308,7 +379,8 @@ static struct vty_app_info msc_vty_info = { .is_config_node = msc_vty_is_config_node, }; -#define DEFAULT_M3UA_REMOTE_IP "127.0.0.1" +#define DEFAULT_M3UA_LOCAL_IP "localhost" +#define DEFAULT_M3UA_REMOTE_IP "localhost" #define DEFAULT_PC "0.23.1" static struct osmo_sccp_instance *sccp_setup(void *ctx, uint32_t cs7_instance, @@ -320,7 +392,7 @@ static struct osmo_sccp_instance *sccp_setup(void *ctx, uint32_t cs7_instance, return osmo_sccp_simple_client_on_ss7_id(ctx, cs7_instance, label, default_pc, OSMO_SS7_ASP_PROT_M3UA, - 0, NULL, /* local: use arbitrary port and 0.0.0.0. */ + 0, DEFAULT_M3UA_LOCAL_IP, /* local: use arbitrary port and 0.0.0.0. */ 0, /* remote: use protocol default port */ DEFAULT_M3UA_REMOTE_IP); /* Note: If a differing remote IP is to be used, it was already entered in the vty config at @@ -377,6 +449,18 @@ static const struct log_info_cat msc_default_categories[] = { .color = "\033[1;32m", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DBCC] = { + .name = "DBCC", + .description = "Layer3 Broadcast Call Control (BCC)", + .color = "\033[1;32m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DGCC] = { + .name = "DGCC", + .description = "Layer3 Group Call Control (GCC)", + .color = "\033[1;32m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, [DMM] = { .name = "DMM", .description = "Layer3 Mobility Management (MM)", @@ -467,6 +551,11 @@ static const struct log_info_cat msc_default_categories[] = { .description = "Supplementary Services", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DASCI] = { + .name = "DASCI", + .description = "Advanced Speech Call Items", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, }; static int filter_fn(const struct log_context *ctx, struct log_target *tar) @@ -490,9 +579,49 @@ extern void *tall_gsms_ctx; extern void *tall_call_ctx; extern void *tall_trans_ctx; +static int msc_mgw_setup(void) +{ + struct mgcp_client *mgcp_client_single; + unsigned int pool_members_initalized; + + /* Initialize MGW pool. This initalizes and connects all MGCP clients that are currently configured in + * the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and + * (re)connect them manually from the VTY. */ + if (!mgcp_client_pool_empty(msc_network->mgw.mgw_pool)) { + pool_members_initalized = mgcp_client_pool_connect(msc_network->mgw.mgw_pool); + if (!pool_members_initalized) { + LOGP(DMSC, LOGL_ERROR, "MGW pool failed to initialize any pool members\n"); + return -EINVAL; + } + LOGP(DMSC, LOGL_NOTICE, + "MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'msc').\n", + pool_members_initalized); + return 0; + } + + /* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool + * member if there is no MGW pool configured. */ + LOGP(DMSC, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'msc'\n"); + mgcp_client_single = mgcp_client_init(msc_network, msc_network->mgw.conf); + if (!mgcp_client_single) { + LOGP(DMSC, LOGL_ERROR, "MGW (single) client initalization failed\n"); + return -EINVAL; + } + if (mgcp_client_connect(mgcp_client_single)) { + LOGP(DMSC, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n", + msc_network->mgw.conf->remote_addr, + msc_network->mgw.conf->remote_port); + return -EINVAL; + } + mgcp_client_pool_register_single(msc_network->mgw.mgw_pool, mgcp_client_single); + + return 0; +} + int main(int argc, char **argv) { int rc; + int ret = 0; struct osmo_sccp_instance *sccp_a; struct osmo_sccp_instance *sccp_iu; @@ -500,7 +629,7 @@ int main(int argc, char **argv) /* Track the use of talloc NULL memory contexts */ talloc_enable_null_tracking(); - osmo_fsm_term_safely(true); + osmo_fsm_set_dealloc_ctx(OTC_SELECT); msc_vty_info.copyright = osmomsc_copyright; @@ -519,16 +648,18 @@ int main(int argc, char **argv) osmo_fsm_log_addr(true); osmo_stats_init(tall_msc_ctx); + rate_ctr_init(tall_msc_ctx); /* For --version, vty_init() must be called before handling options */ vty_init(&msc_vty_info); - osmo_ss7_init(); + OSMO_ASSERT(osmo_ss7_init() == 0); osmo_ss7_vty_init_asp(tall_msc_ctx); osmo_sccp_vty_init(); - - /* Parse options */ - handle_options(argc, argv); + ctrl_vty_init(tall_msc_ctx); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_cpu_sched_vty_init(tall_msc_ctx); /* Allocate global gsm_network struct. * At first set the internal MNCC as default, may be changed below according to cfg or cmdline option. */ @@ -536,6 +667,11 @@ int main(int argc, char **argv) if (!msc_network) return -ENOMEM; + msc_vty_init(msc_network); + + /* Parse options */ + handle_options(argc, argv); + call_leg_init(msc_network); mncc_call_fsm_init(msc_network); @@ -544,13 +680,8 @@ int main(int argc, char **argv) exit(1); } - ctrl_vty_init(tall_msc_ctx); - logging_vty_add_cmds(); - osmo_talloc_vty_add_cmds(); - msc_vty_init(msc_network); - #ifdef BUILD_SMPP - if (smpp_openbsc_alloc_init(tall_msc_ctx) < 0) + if (smpp_msc_alloc_init(tall_msc_ctx) < 0) return -1; #endif sgs_iface_init(tall_msc_ctx, msc_network); @@ -584,8 +715,7 @@ int main(int argc, char **argv) DEBUGP(DMNCC, "Using internal MNCC handler.\n"); /* start telnet after reading config for vty_get_bind_addr() */ - rc = telnet_init_dynif(tall_msc_ctx, &msc_network, - vty_get_bind_addr(), OSMO_VTY_PORT_MSC); + rc = telnet_init_default(tall_msc_ctx, &msc_network, OSMO_VTY_PORT_MSC); if (rc < 0) return 2; @@ -594,28 +724,27 @@ int main(int argc, char **argv) * following code until iu_init() is legacy. */ #ifdef BUILD_SMPP - smpp_openbsc_start(msc_network); + smpp_msc_start(msc_network); #endif /* start control interface after reading config for * ctrl_vty_get_bind_addr() */ - msc_network->ctrl = ctrl_interface_setup_dynip(msc_network, ctrl_vty_get_bind_addr(), - OSMO_CTRL_PORT_MSC, NULL); + msc_network->ctrl = ctrl_interface_setup(msc_network, OSMO_CTRL_PORT_MSC, NULL); if (!msc_network->ctrl) { - printf("Failed to initialize control interface. Exiting.\n"); + fprintf(stderr, "Failed to initialize control interface. Exiting.\n"); return -1; } #if 0 TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_install(). if (bsc_base_ctrl_cmds_install() != 0) { - printf("Failed to initialize the BSC control commands.\n"); + fprintf(stderr, "Failed to initialize the BSC control commands.\n"); return -1; } #endif if (msc_ctrl_cmds_install(msc_network) != 0) { - printf("Failed to initialize the MSC control commands.\n"); + fprintf(stderr, "Failed to initialize the MSC control commands.\n"); return -1; } @@ -624,12 +753,6 @@ TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_i /* TODO: is this used for crypto?? Improve randomness, at least we * should try to use the nanoseconds part of the current time. */ - if (db_init(msc_cmdline_config.database_name)) { - printf("DB: Failed to init database: %s\n", - msc_cmdline_config.database_name); - return 4; - } - if (msc_gsup_client_start(msc_network)) { fprintf(stderr, "Failed to start GSUP client\n"); exit(1); @@ -642,11 +765,6 @@ TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_i exit(1); } - if (db_prepare()) { - printf("DB: Failed to prepare database.\n"); - return 5; - } - signal(SIGINT, &signal_handler); signal(SIGTERM, &signal_handler); signal(SIGABRT, &signal_handler); @@ -655,33 +773,35 @@ TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_i osmo_init_ignore_signals(); /* start the SMS queue */ - if (sms_queue_start(msc_network, 20) != 0) - return -1; - - msc_network->mgw.client = mgcp_client_init( - msc_network, &msc_network->mgw.conf); + if (sms_queue_start(msc_network) != 0) { + ret = -1; + goto error; + } - if (mgcp_client_connect(msc_network->mgw.client)) { - printf("MGCPGW connect failed\n"); - return 7; + if (msc_mgw_setup() != 0) { + ret = 7; + goto error; } if (ss7_setup(tall_msc_ctx, &sccp_a, &sccp_iu)) { - printf("Setting up SCCP client failed.\n"); - return 8; + fprintf(stderr, "Setting up SCCP client failed.\n"); + ret = 8; + goto error; } if (sgs_server_open(g_sgs)) { - printf("Starting SGs server failed\n"); - return 9; + fprintf(stderr, "Starting SGs server failed\n"); + ret = 9; + goto error; } msc_network->a.sri = sccp_ran_init(msc_network, sccp_a, OSMO_SCCP_SSN_BSSAP, "OsmoMSC-A", &msc_ran_infra[OSMO_RAT_GERAN_A], msc_network); if (!msc_network->a.sri) { - printf("Setting up A receiver failed\n"); - return 10; + fprintf(stderr, "Setting up A receiver failed\n"); + ret = 10; + goto error; } LOGP(DMSC, LOGL_NOTICE, "A-interface: SCCP user %s, cs7-instance %u (%s)\n", osmo_sccp_user_name(msc_network->a.sri->scu), @@ -695,8 +815,9 @@ TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_i "OsmoMSC-IuCS", &msc_ran_infra[OSMO_RAT_UTRAN_IU], msc_network); if (!msc_network->iu.sri) { - printf("Setting up IuCS receiver failed\n"); - return 11; + fprintf(stderr, "Setting up IuCS receiver failed\n"); + ret = 11; + goto error; } /* Compatibility with legacy osmo-hnbgw that was unable to properly handle RESET messages. */ @@ -715,19 +836,32 @@ TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_i rc = osmo_daemonize(); if (rc < 0) { perror("Error during daemonize"); - return 6; + ret = 6; + goto error; } } - while (!quit) { + do { log_reset_context(); - osmo_select_main(0); - } + osmo_select_main_ctx(0); - msc_network_shutdown(msc_network); - osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); - sleep(3); + /* If the user hits Ctrl-C the third time, just terminate immediately. */ + if (quit >= 3) + break; + /* Has SIGTERM been received (and not yet been handled)? */ + if (quit && !osmo_select_shutdown_requested()) { + msc_network_shutdown(msc_network); + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + + /* Request write-only mode in osmo_select_main_ctx() */ + osmo_select_shutdown_request(); + /* continue the main select loop until all write queues are serviced. */ + } + } while (!osmo_select_shutdown_done()); + +error: + db_fini(); log_fini(); /** @@ -746,5 +880,5 @@ TODO: we probably want some of the _net_ ctrl commands from bsc_base_ctrl_cmds_i */ talloc_report_full(NULL, stderr); talloc_disable_null_tracking(); - return 0; + return ret; } diff --git a/src/utils/Makefile.am b/src/utils/Makefile.am index cb0faf69f..606967245 100644 --- a/src/utils/Makefile.am +++ b/src/utils/Makefile.am @@ -34,12 +34,26 @@ smpp_mirror_SOURCES = \ smpp_mirror_CFLAGS = \ $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOSCCP_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(LIBSMPP34_CFLAGS) \ $(NULL) smpp_mirror_LDADD = \ + $(top_builddir)/src/libsmpputil/libsmpputil.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libvlr/libvlr.a \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMONETIF_LIBS) \ $(LIBSMPP34_LIBS) \ + $(LIBOSMORANAP_LIBS) \ + $(LIBASN1C_LIBS) \ + $(LIBOSMOSIGTRAN_LIBS) \ + $(LIBOSMOMGCPCLIENT_LIBS) \ + $(LIBOSMOGSUPCLIENT_LIBS) \ + $(LIBSQLITE3_LIBS) \ + -lsctp \ $(NULL) endif diff --git a/src/utils/smpp_mirror.c b/src/utils/smpp_mirror.c index 30535535b..3356468ae 100644 --- a/src/utils/smpp_mirror.c +++ b/src/utils/smpp_mirror.c @@ -19,80 +19,9 @@ #include <osmocom/core/write_queue.h> #include <osmocom/msc/debug.h> +#include <osmocom/smpp/smpp.h> -/* FIXME: merge with smpp_smsc.c */ -#define SMPP_SYS_ID_LEN 16 -enum esme_read_state { - READ_ST_IN_LEN = 0, - READ_ST_IN_MSG = 1, -}; -/* FIXME: merge with smpp_smsc.c */ - -struct esme { - struct osmo_fd ofd; - - uint32_t own_seq_nr; - - struct osmo_wqueue wqueue; - enum esme_read_state read_state; - uint32_t read_len; - uint32_t read_idx; - struct msgb *read_msg; - - uint8_t smpp_version; - char system_id[SMPP_SYS_ID_LEN+1]; - char password[SMPP_SYS_ID_LEN+1]; -}; - -/* FIXME: merge with smpp_smsc.c */ -#define SMPP34_UNPACK(rc, type, str, data, len) \ - memset(str, 0, sizeof(*str)); \ - rc = smpp34_unpack(type, str, data, len) -#define INIT_RESP(type, resp, req) { \ - memset((resp), 0, sizeof(*(resp))); \ - (resp)->command_length = 0; \ - (resp)->command_id = type; \ - (resp)->command_status = ESME_ROK; \ - (resp)->sequence_number = (req)->sequence_number; \ -} -#define PACK_AND_SEND(esme, ptr) pack_and_send(esme, (ptr)->command_id, ptr) -static inline uint32_t smpp_msgb_cmdid(struct msgb *msg) -{ - uint8_t *tmp = msgb_data(msg) + 4; - return ntohl(*(uint32_t *)tmp); -} -static uint32_t esme_inc_seq_nr(struct esme *esme) -{ - esme->own_seq_nr++; - if (esme->own_seq_nr > 0x7fffffff) - esme->own_seq_nr = 1; - return esme->own_seq_nr; -} -static int pack_and_send(struct esme *esme, uint32_t type, void *ptr) -{ - struct msgb *msg = msgb_alloc(4096, "SMPP_Tx"); - int rc, rlen; - if (!msg) - return -ENOMEM; - - rc = smpp34_pack(type, msg->tail, msgb_tailroom(msg), &rlen, ptr); - if (rc != 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Error during smpp34_pack(): %s\n", - esme->system_id, smpp34_strerror); - msgb_free(msg); - return -EINVAL; - } - msgb_put(msg, rlen); - - if (osmo_wqueue_enqueue(&esme->wqueue, msg) != 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Write queue full. Dropping message\n", - esme->system_id); - msgb_free(msg); - return -EAGAIN; - } - return 0; -} /* FIXME: merge with smpp_smsc.c */ static struct tlv_t *find_tlv(struct tlv_t *head, uint16_t tag) @@ -188,8 +117,8 @@ static int bind_transceiver(struct esme *esme) memset(&bind, 0, sizeof(bind)); bind.command_id = BIND_TRANSCEIVER; bind.sequence_number = esme_inc_seq_nr(esme); - snprintf((char *)bind.system_id, sizeof(bind.system_id), "%s", esme->system_id); - snprintf((char *)bind.password, sizeof(bind.password), "%s", esme->password); + snprintf((char *)bind.system_id, SMPP_SYS_ID_LEN + 1, "%s", esme->system_id); + snprintf((char *)bind.password, SMPP_SYS_ID_LEN + 1, "%s", esme->password); snprintf((char *)bind.system_type, sizeof(bind.system_type), "mirror"); bind.interface_version = esme->smpp_version; @@ -214,6 +143,14 @@ static int smpp_pdu_rx(struct esme *esme, struct msgb *msg) return rc; } +static void esme_read_state_reset(struct esme *esme) +{ + esme->read_msg = NULL; + esme->read_idx = 0; + esme->read_len = 0; + esme->read_state = READ_ST_IN_LEN; +} + /* FIXME: merge with smpp_smsc.c */ static int esme_read_cb(struct osmo_fd *ofd) { @@ -230,14 +167,17 @@ static int esme_read_cb(struct osmo_fd *ofd) rdlen = sizeof(uint32_t) - esme->read_idx; rc = read(ofd->fd, lenptr + esme->read_idx, rdlen); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %d\n", - esme->system_id, rc); + LOGPESME(esme, LOGL_ERROR, "read returned %d\n", rc); } else if (rc == 0) { goto dead_socket; } else esme->read_idx += rc; if (esme->read_idx >= sizeof(uint32_t)) { esme->read_len = ntohl(len); + if (esme->read_len > 65535) { + /* unrealistic */ + goto dead_socket; + } msg = msgb_alloc(esme->read_len, "SMPP Rx"); if (!msg) return -ENOMEM; @@ -253,8 +193,7 @@ static int esme_read_cb(struct osmo_fd *ofd) rdlen = esme->read_len - esme->read_idx; rc = read(ofd->fd, msg->tail, OSMO_MIN(rdlen, msgb_tailroom(msg))); if (rc < 0) { - LOGP(DSMPP, LOGL_ERROR, "[%s] read returned %d\n", - esme->system_id, rc); + LOGPESME(esme, LOGL_ERROR, "read returned %d\n", rc); } else if (rc == 0) { goto dead_socket; } else { @@ -264,10 +203,7 @@ static int esme_read_cb(struct osmo_fd *ofd) if (esme->read_idx >= esme->read_len) { rc = smpp_pdu_rx(esme, esme->read_msg); - esme->read_msg = NULL; - esme->read_idx = 0; - esme->read_len = 0; - esme->read_state = READ_ST_IN_LEN; + esme_read_state_reset(esme); } break; } @@ -278,6 +214,7 @@ dead_socket: osmo_fd_unregister(&esme->wqueue.bfd); close(esme->wqueue.bfd.fd); esme->wqueue.bfd.fd = -1; + esme_read_state_reset(esme); exit(2342); return 0; @@ -295,7 +232,7 @@ static int esme_write_cb(struct osmo_fd *ofd, struct msgb *msg) esme->wqueue.bfd.fd = -1; exit(99); } else if (rc < msgb_length(msg)) { - LOGP(DSMPP, LOGL_ERROR, "[%s] Short write\n", esme->system_id); + LOGPESME(esme, LOGL_ERROR, "Short write\n"); return 0; } @@ -307,11 +244,8 @@ static int smpp_esme_init(struct esme *esme, const char *host, uint16_t port) int rc; if (port == 0) - port = 2775; + port = SMPP_PORT; - esme->own_seq_nr = rand(); - esme_inc_seq_nr(esme); - osmo_wqueue_init(&esme->wqueue, 10); esme->wqueue.bfd.data = esme; esme->wqueue.read_cb = esme_read_cb; esme->wqueue.write_cb = esme_write_cb; @@ -339,7 +273,7 @@ const struct log_info log_info = { int main(int argc, char **argv) { - struct esme esme; + struct esme *esme; char *host = "localhost"; int port = 0; int rc; @@ -347,20 +281,22 @@ int main(int argc, char **argv) msgb_talloc_ctx_init(ctx, 0); - memset(&esme, 0, sizeof(esme)); - osmo_init_logging2(ctx, &log_info); - snprintf((char *) esme.system_id, sizeof(esme.system_id), "mirror"); - snprintf((char *) esme.password, sizeof(esme.password), "mirror"); - esme.smpp_version = 0x34; + esme = esme_alloc(ctx); + if (!esme) + exit(2); + + snprintf((char *) esme->system_id, sizeof(esme->system_id), "mirror"); + snprintf((char *) esme->password, sizeof(esme->password), "mirror"); + esme->smpp_version = 0x34; if (argc >= 2) host = argv[1]; if (argc >= 3) port = atoi(argv[2]); - rc = smpp_esme_init(&esme, host, port); + rc = smpp_esme_init(esme, host, port); if (rc < 0) exit(1); |