diff --git a/include/osmocom/msc/debug.h b/include/osmocom/msc/debug.h index 3347e20d4..0d08ceb11 100644 --- a/include/osmocom/msc/debug.h +++ b/include/osmocom/msc/debug.h @@ -8,6 +8,7 @@ enum { DCC, DMM, DRR, + DLCLS, DMNCC, DPAG, DMSC, diff --git a/include/osmocom/msc/gsm_data.h b/include/osmocom/msc/gsm_data.h index 170d88455..72e33a61d 100644 --- a/include/osmocom/msc/gsm_data.h +++ b/include/osmocom/msc/gsm_data.h @@ -260,6 +260,10 @@ struct gsm_network { /* Whether to use call waiting on the network */ bool call_waiting; + + /* Whether to use lcls on the network */ + bool lcls_permitted; + char *sms_db_file_path; }; diff --git a/include/osmocom/msc/mncc.h b/include/osmocom/msc/mncc.h index 1c8aff0c9..500222763 100644 --- a/include/osmocom/msc/mncc.h +++ b/include/osmocom/msc/mncc.h @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -160,7 +161,7 @@ struct gsm_mncc { unsigned char lchan_type; unsigned char lchan_mode; - + struct osmo_gcr_parsed gcr; /* A buffer to contain SDP ('\0' terminated) */ char sdp[1024]; }; @@ -171,7 +172,7 @@ struct gsm_data_frame { unsigned char data[0]; }; -#define MNCC_SOCK_VERSION 7 +#define MNCC_SOCK_VERSION 8 struct gsm_mncc_hello { uint32_t msg_type; uint32_t version; diff --git a/include/osmocom/msc/ran_msg.h b/include/osmocom/msc/ran_msg.h index 1303ba3f9..fd2439fef 100644 --- a/include/osmocom/msc/ran_msg.h +++ b/include/osmocom/msc/ran_msg.h @@ -88,6 +88,7 @@ struct ran_assignment_command { uint8_t osmux_cid; bool call_id_present; uint32_t call_id; + struct osmo_lcls *lcls; }; struct ran_cipher_mode_command { diff --git a/include/osmocom/msc/transaction.h b/include/osmocom/msc/transaction.h index 928b137ae..14b89bb81 100644 --- a/include/osmocom/msc/transaction.h +++ b/include/osmocom/msc/transaction.h @@ -100,6 +100,7 @@ struct gsm_trans { struct osmo_timer_list timer_guard; struct gsm_mncc msg; /* stores setup/disconnect/release message */ bool mncc_initiated; /* Whether an MNCC Release is necessary on failure */ + struct osmo_lcls *lcls; } cc; struct { struct gsm411_smc_inst smc_inst; @@ -145,6 +146,8 @@ struct gsm_trans *trans_find_by_sm_rp_mr(const struct gsm_network *net, const struct vlr_subscr *vsub, uint8_t sm_rp_mr); +struct osmo_lcls *trans_lcls_compose(const struct gsm_trans *trans, bool use_lac); + struct gsm_trans *trans_alloc(struct gsm_network *net, struct vlr_subscr *vsub, enum trans_type type, uint8_t trans_id, diff --git a/src/libmsc/gsm_04_08_cc.c b/src/libmsc/gsm_04_08_cc.c index a8b4665e6..d6a2864e7 100644 --- a/src/libmsc/gsm_04_08_cc.c +++ b/src/libmsc/gsm_04_08_cc.c @@ -318,6 +318,16 @@ 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.lcls->gcr = trans->cc.msg.gcr; + 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); @@ -502,6 +512,14 @@ 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) + setup.gcr = trans->cc.lcls->gcr; + 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) { diff --git a/src/libmsc/msc_a.c b/src/libmsc/msc_a.c index fa8e8428b..9b6b60223 100644 --- a/src/libmsc/msc_a.c +++ b/src/libmsc/msc_a.c @@ -569,6 +569,7 @@ static void msc_a_call_leg_ran_local_addr_available(struct msc_a *msc_a) .osmux_cid = msc_a->cc.call_leg->rtp[RTP_TO_RAN]->local_osmux_cid, .call_id_present = true, .call_id = cc_trans->callref, + .lcls = cc_trans->cc.lcls, }, }; if (msc_a_ran_down(msc_a, MSC_ROLE_I, &msg)) { @@ -1506,6 +1507,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; diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c index 2a4dbbb82..e4e093741 100644 --- a/src/libmsc/msc_vty.c +++ b/src/libmsc/msc_vty.c @@ -499,6 +499,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>", @@ -764,6 +782,8 @@ static int config_write_msc(struct vty *vty) 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); @@ -2083,6 +2103,8 @@ void msc_vty_init(struct gsm_network *msc_network) 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); diff --git a/src/libmsc/ran_msg_a.c b/src/libmsc/ran_msg_a.c index 273f8dddd..b50259d04 100644 --- a/src/libmsc/ran_msg_a.c +++ b/src/libmsc/ran_msg_a.c @@ -1004,7 +1004,8 @@ static struct msgb *ran_a_make_assignment_command(struct osmo_fsm_inst *log_fi, if(ac->call_id_present == true) call_id = &ac->call_id; - msg = gsm0808_create_ass(ac->channel_type, NULL, use_rtp_addr, use_scl, call_id); + msg = gsm0808_create_ass2(ac->channel_type, NULL, use_rtp_addr, use_scl, call_id, + NULL, ac->lcls); if (ac->osmux_present) _gsm0808_assignment_extend_osmux(msg, ac->osmux_cid); return msg; diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c index 94712cc89..2108ab492 100644 --- a/src/libmsc/transaction.c +++ b/src/libmsc/transaction.c @@ -110,6 +110,66 @@ 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->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) { + LOGP(DCC, LOGL_ERROR, "LCLS: unable to fill parameters for unallocated transaction\n"); + return NULL; + } + + 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->net->plmn); + + osmo_store32be(trans->callref, lcls->gcr.cr); + 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 %s\n", use_lac ? "LAC" : "CI", + osmo_hexdump(lcls->gcr.cr, 5)); + + 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"; diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c index a0f584db8..cd91d54f7 100644 --- a/src/osmo-msc/msc_main.c +++ b/src/osmo-msc/msc_main.c @@ -258,6 +258,7 @@ struct gsm_network *msc_network_alloc(void *ctx, mgcp_client_conf_init(&net->mgw.conf); net->call_waiting = true; + net->lcls_permitted = false; net->mgw.tdefs = g_mgw_tdefs; osmo_tdefs_reset(net->mgw.tdefs); diff --git a/tests/mncc/mncc_test.ok b/tests/mncc/mncc_test.ok index 933470688..7cb12015e 100644 --- a/tests/mncc/mncc_test.ok +++ b/tests/mncc/mncc_test.ok @@ -1,15 +1,15 @@ test_sdp_termination() struct gsm_mncc: -empty SDP: len=1860 sdplen=1026 sdp="\0" rc=0 -empty SDP, shortest possible: len=835 sdplen=1 sdp="\0" rc=0 -empty SDP, zero len: len=834 sdplen=0 sdp=- rc=-22 -terminated SDP str: len=1860 sdplen=1026 sdp="Privacy is a desirable marketing option\0" rc=0 -terminated SDP str, shortest possible: len=874 sdplen=40 sdp="Privacy is a desirable marketing option\0" rc=0 -terminated SDP str, but len excludes nul: len=873 sdplen=39 sdp="Privacy is a desirable marketing option" rc=-22 -terminated SDP str, but len too short: len=857 sdplen=23 sdp="Privacy is a desirable " rc=-22 -len way too short: len=10 sdplen=-824 sdp=- rc=-22 -len zero: len=0 sdplen=-834 sdp=- rc=-22 +empty SDP: len=1872 sdplen=1024 sdp="\0" rc=0 +empty SDP, shortest possible: len=849 sdplen=1 sdp="\0" rc=0 +empty SDP, zero len: len=848 sdplen=0 sdp=- rc=-22 +terminated SDP str: len=1872 sdplen=1024 sdp="Privacy is a desirable marketing option\0" rc=0 +terminated SDP str, shortest possible: len=888 sdplen=40 sdp="Privacy is a desirable marketing option\0" rc=0 +terminated SDP str, but len excludes nul: len=887 sdplen=39 sdp="Privacy is a desirable marketing option" rc=-22 +terminated SDP str, but len too short: len=871 sdplen=23 sdp="Privacy is a desirable " rc=-22 +len way too short: len=10 sdplen=-838 sdp=- rc=-22 +len zero: len=0 sdplen=-848 sdp=- rc=-22 struct gsm_mncc_rtp: empty SDP: len=1168 sdplen=1024 sdp="\0" rc=0 diff --git a/tests/test_nodes.vty b/tests/test_nodes.vty index 268681a01..315264fd1 100644 --- a/tests/test_nodes.vty +++ b/tests/test_nodes.vty @@ -48,6 +48,8 @@ OsmoMSC(config-msc)# list ... sms-database PATH assign-tmsi + lcls-permitted + no lcls-permitted mncc internal mncc external MNCC_SOCKET_PATH mncc guard-timeout <0-255>