From 2188a778b54562948641abf7b2597ef2a7d1dae6 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Fri, 20 May 2016 21:59:55 +0200 Subject: Implement IuCS (large refactoring and addition) osmo-nitb becomes osmo-msc add DIUCS debug log constant add iucs.[hc] add msc vty, remove nitb vty add libiudummy, to avoid linking Iu deps in tests Use new msc_tx_dtap() instead of gsm0808_submit_dtap() libmgcp: add mgcpgw client API bridge calls via mgcpgw Enable MSC specific CTRL commands, bsc_base_ctrl_cmds_install() still needs to be split up. Change-Id: I5b5b6a9678b458affa86800afb1ec726e66eed88 --- src/Makefile.am | 2 +- src/gprs/gprs_gmm.c | 10 +- src/gprs/gprs_sgsn.c | 1 + src/gprs/sgsn_libgtp.c | 2 +- src/gprs/sgsn_main.c | 9 - src/gprs/sgsn_vty.c | 14 + src/libbsc/bsc_init.c | 6 - src/libbsc/paging.c | 6 + src/libcommon/debug.c | 5 + src/libcommon/gsm_data.c | 7 + src/libiu/iu.c | 53 +++- src/libiu/iu_vty.c | 72 ++++- src/libmgcp/Makefile.am | 3 + src/libmgcp/mgcp_common.c | 54 ++++ src/libmgcp/mgcp_network.c | 101 ++++++- src/libmgcp/mgcp_protocol.c | 23 +- src/libmgcp/mgcpgw_client.c | 549 ++++++++++++++++++++++++++++++++++++++ src/libmgcp/mgcpgw_client_vty.c | 116 ++++++++ src/libmsc/Makefile.am | 9 + src/libmsc/a_iface.c | 8 + src/libmsc/gsm_04_08.c | 152 ++++++++--- src/libmsc/gsm_04_11.c | 7 +- src/libmsc/gsm_04_80.c | 10 +- src/libmsc/gsm_subscriber.c | 91 +++++-- src/libmsc/iu_dummy.c | 93 +++++++ src/libmsc/iucs.c | 191 +++++++++++++ src/libmsc/iucs_ranap.c | 104 ++++++++ src/libmsc/msc_ifaces.c | 238 ++++++++++++++++- src/libmsc/msc_vty.c | 130 +++++++++ src/libmsc/osmo_msc.c | 73 ++--- src/libmsc/silent_call.c | 3 +- src/libmsc/subscr_conn.c | 56 +++- src/libmsc/transaction.c | 6 +- src/libmsc/vty_interface_layer3.c | 156 +---------- src/libvlr/vlr.c | 4 + src/libvlr/vlr_access_req_fsm.c | 12 + src/libvlr/vlr_lu_fsm.c | 12 + src/osmo-bsc/osmo_bsc_api.c | 2 +- src/osmo-msc/Makefile.am | 57 ++++ src/osmo-msc/msc_main.c | 514 +++++++++++++++++++++++++++++++++++ src/osmo-nitb/Makefile.am | 46 ---- src/osmo-nitb/bsc_hack.c | 412 ---------------------------- 42 files changed, 2616 insertions(+), 803 deletions(-) create mode 100644 src/libmgcp/mgcp_common.c create mode 100644 src/libmgcp/mgcpgw_client.c create mode 100644 src/libmgcp/mgcpgw_client_vty.c create mode 100644 src/libmsc/iu_dummy.c create mode 100644 src/libmsc/iucs.c create mode 100644 src/libmsc/iucs_ranap.c create mode 100644 src/libmsc/msc_vty.c create mode 100644 src/osmo-msc/Makefile.am create mode 100644 src/osmo-msc/msc_main.c delete mode 100644 src/osmo-nitb/Makefile.am delete mode 100644 src/osmo-nitb/bsc_hack.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index c66f9e56b..7e9e1dca7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,7 +40,7 @@ endif # Programs SUBDIRS += \ - osmo-nitb \ + osmo-msc \ osmo-bsc_mgcp \ utils \ ipaccess \ diff --git a/src/gprs/gprs_gmm.c b/src/gprs/gprs_gmm.c index 577af1bf3..15e2fedf6 100644 --- a/src/gprs/gprs_gmm.c +++ b/src/gprs/gprs_gmm.c @@ -295,6 +295,10 @@ static void msgid2mmctx(struct sgsn_mm_ctx *mm, const struct msgb *msg) mm->gb.nsei = msgb_nsei(msg); /* In case a Iu connection is reconnected we need to update the ue ctx */ mm->iu.ue_ctx = msg->dst; + if (mm->ran_type == MM_CTX_T_UTRAN_Iu + && mm->iu.ue_ctx) + mm->iu.ue_ctx->rab_assign_addr_enc = + sgsn->cfg.iu.rab_assign_addr_enc; } /* Store BVCI/NSEI in MM context */ @@ -978,7 +982,7 @@ void activate_pdp_rabs(struct sgsn_mm_ctx *ctx) if (ctx->ran_type != MM_CTX_T_UTRAN_Iu) return; llist_for_each_entry(pdp, &ctx->pdp_list, list) { - iu_rab_act_ps(pdp->nsapi, pdp, 1); + iu_rab_act_ps(pdp->nsapi, pdp); } } #endif @@ -2897,14 +2901,16 @@ int gprs_gmm_rx_resume(struct gprs_ra_id *raid, uint32_t tlli, } #ifdef BUILD_IU -int iu_rab_act_ps(uint8_t rab_id, struct sgsn_pdp_ctx *pdp, bool use_x213_nsap) +int iu_rab_act_ps(uint8_t rab_id, struct sgsn_pdp_ctx *pdp) { struct msgb *msg; struct sgsn_mm_ctx *mm = pdp->mm; struct ue_conn_ctx *uectx; uint32_t ggsn_ip; + bool use_x213_nsap; uectx = mm->iu.ue_ctx; + use_x213_nsap = (uectx->rab_assign_addr_enc == NSAP_ADDR_ENC_X213); /* Get the IP address for ggsn user plane */ memcpy(&ggsn_ip, pdp->lib->gsnru.v, pdp->lib->gsnru.l); diff --git a/src/gprs/gprs_sgsn.c b/src/gprs/gprs_sgsn.c index 071dd97c8..18625aefe 100644 --- a/src/gprs/gprs_sgsn.c +++ b/src/gprs/gprs_sgsn.c @@ -247,6 +247,7 @@ struct sgsn_mm_ctx *sgsn_mm_ctx_alloc_iu(void *uectx) ctx->ran_type = MM_CTX_T_UTRAN_Iu; ctx->iu.ue_ctx = uectx; + ctx->iu.ue_ctx->rab_assign_addr_enc = sgsn->cfg.iu.rab_assign_addr_enc; ctx->iu.new_key = 1; ctx->gmm_state = GMM_DEREGISTERED; ctx->pmm_state = PMM_DETACHED; diff --git a/src/gprs/sgsn_libgtp.c b/src/gprs/sgsn_libgtp.c index cd1093167..7595bf83c 100644 --- a/src/gprs/sgsn_libgtp.c +++ b/src/gprs/sgsn_libgtp.c @@ -407,7 +407,7 @@ static int create_pdp_conf(struct pdp_t *pdp, void *cbp, int cause) } else if (pctx->mm->ran_type == MM_CTX_T_UTRAN_Iu) { #ifdef BUILD_IU /* Activate a radio bearer */ - iu_rab_act_ps(pdp->nsapi, pctx, 1); + iu_rab_act_ps(pdp->nsapi, pctx); return 0; #else return -ENOTSUP; diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c index d871939e5..71cb18c96 100644 --- a/src/gprs/sgsn_main.c +++ b/src/gprs/sgsn_main.c @@ -319,12 +319,6 @@ static const struct log_info gprs_log_info = { .num_cat = ARRAY_SIZE(gprs_categories), }; -/* Implement the extern asn_debug from libasn1c to indicate whether the ASN.1 - * binary code decoded and encoded during Iu communication should be logged to - * stderr. See osmocom's libasn1c, asn_internal.h, at "if (asn_debug)": - * http://git.osmocom.org/libasn1c/tree/include/asn1c/asn_internal.h */ -int asn_debug = 0; - int sgsn_ranap_iu_event(struct ue_conn_ctx *ctx, enum iu_event_type type, void *data); int main(int argc, char **argv) @@ -352,9 +346,6 @@ int main(int argc, char **argv) osmo_stats_vty_add_cmds(&gprs_log_info); sgsn_vty_init(&sgsn_inst.cfg); ctrl_vty_init(tall_bsc_ctx); -#ifdef BUILD_IU - iu_vty_init(&asn_debug); -#endif handle_options(argc, argv); diff --git a/src/gprs/sgsn_vty.c b/src/gprs/sgsn_vty.c index 1cefe37cf..888f53a13 100644 --- a/src/gprs/sgsn_vty.c +++ b/src/gprs/sgsn_vty.c @@ -44,6 +44,12 @@ #include +#include "../../bscconfig.h" + +#ifdef BUILD_IU +#include +#endif + static struct sgsn_config *g_cfg = NULL; const struct value_string sgsn_auth_pol_strs[] = { @@ -297,6 +303,10 @@ static int config_write_sgsn(struct vty *vty) } else vty_out(vty, " no compression v42bis%s", VTY_NEWLINE); +#ifdef BUILD_IU + iu_vty_config_write(vty, " "); +#endif + return CMD_SUCCESS; } @@ -1284,6 +1294,10 @@ int sgsn_vty_init(struct sgsn_config *cfg) install_element(SGSN_NODE, &cfg_no_comp_v42bis_cmd); install_element(SGSN_NODE, &cfg_comp_v42bis_cmd); install_element(SGSN_NODE, &cfg_comp_v42bisp_cmd); + +#ifdef BUILD_IU + iu_vty_init(SGSN_NODE, &g_cfg->iu.rab_assign_addr_enc); +#endif return 0; } diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c index e12b8801f..64dcd157b 100644 --- a/src/libbsc/bsc_init.c +++ b/src/libbsc/bsc_init.c @@ -516,12 +516,6 @@ int bsc_network_alloc(mncc_recv_cb_t mncc_recv) bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC"); bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC"); - /* TODO: move to libmsc when gsm_network is split between libbsc and - * libmsc */ - bsc_gsmnet->gsup_server_addr_str = talloc_strdup(bsc_gsmnet, - MSC_HLR_REMOTE_IP_DEFAULT); - bsc_gsmnet->gsup_server_port = MSC_HLR_REMOTE_PORT_DEFAULT; - return 0; } diff --git a/src/libbsc/paging.c b/src/libbsc/paging.c index 8c1445cc6..e19c2d1c4 100644 --- a/src/libbsc/paging.c +++ b/src/libbsc/paging.c @@ -56,6 +56,12 @@ void *tall_paging_ctx; #define PAGING_TIMER 0, 500000 +/* + * TODO MSCSPLIT: the paging in libbsc is closely tied to MSC land in that the + * MSC realm callback functions used to be invoked from the BSC/BTS level. So + * this entire file needs to be rewired for use with an A interface. + */ + /* * Kill one paging request update the internal list... */ diff --git a/src/libcommon/debug.c b/src/libcommon/debug.c index dc79a843e..7dbbc6ac0 100644 --- a/src/libcommon/debug.c +++ b/src/libcommon/debug.c @@ -184,6 +184,11 @@ static const struct log_info_cat default_categories[] = { .description = "Visitor Location Register", .enabled = 1, .loglevel = LOGL_DEBUG, }, + [DIUCS] = { + .name = "DIUCS", + .description = "Iu-CS Protocol", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, }; static int filter_fn(const struct log_context *ctx, struct log_target *tar) diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c index f6fde37bd..b5bf0599e 100644 --- a/src/libcommon/gsm_data.c +++ b/src/libcommon/gsm_data.c @@ -449,3 +449,10 @@ bool classmark_is_r99(struct gsm_classmark *cm) rev_lev = (cm->classmark2[0] >> 5) & 0x3; return rev_lev >= 2; } + +const struct value_string ran_type_names[] = { + OSMO_VALUE_STRING(RAN_UNKNOWN), + OSMO_VALUE_STRING(RAN_GERAN_A), + OSMO_VALUE_STRING(RAN_UTRAN_IU), + { 0, NULL } +}; diff --git a/src/libiu/iu.c b/src/libiu/iu.c index 932b21718..5d56a4a37 100644 --- a/src/libiu/iu.c +++ b/src/libiu/iu.c @@ -80,7 +80,15 @@ struct iu_rnc { void *talloc_iu_ctx; -int asn1_xer_print = 1; +/* Implement the extern asn_debug from libasn1c to indicate whether to print + * asn.1 debug messages (see libasn1c). */ +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; + void *talloc_asn1_ctx; iu_recv_cb_t global_iu_recv_cb = NULL; @@ -241,6 +249,25 @@ int iu_tx_sec_mode_cmd(struct ue_conn_ctx *uectx, struct gsm_auth_tuple *tp, return 0; } +int iu_tx_common_id(struct ue_conn_ctx *uectx, const char *imsi) +{ + struct msgb *msg; + struct osmo_scu_prim *prim; + + LOGP(DRANAP, LOGL_INFO, "Transmitting RANAP CommonID (SUA link %p conn_id %u)\n", + uectx->link, uectx->conn_id); + + msg = ranap_new_msg_common_id(imsi); + msg->l2h = msg->data; + prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim)); + prim->u.data.conn_id = uectx->conn_id; + osmo_prim_init(&prim->oph, SCCP_SAP_USER, + OSMO_SCU_PRIM_N_DATA, + PRIM_OP_REQUEST, msg); + osmo_sua_user_link_down(uectx->link, &prim->oph); + return 0; +} + static int iu_grnc_id_parse(struct iu_grnc_id *dst, struct RANAP_GlobalRNC_ID *src) { @@ -375,20 +402,35 @@ int iu_tx(struct msgb *msg_nas, uint8_t sapi) return 0; } -static int ranap_handle_co_iu_rel_req(struct ue_conn_ctx *ctx, RANAP_Iu_ReleaseRequestIEs_t *ies) +/* Send Iu Release for the given UE connection. + * If cause is NULL, the standard "No remaining RAB" cause is sent, otherwise + * the provided cause. */ +int iu_tx_release(struct ue_conn_ctx *ctx, const struct RANAP_Cause *cause) { struct msgb *msg; struct osmo_scu_prim *prim; + static const struct RANAP_Cause default_cause = { + .present = RANAP_Cause_PR_radioNetwork, + .choice.radioNetwork = RANAP_CauseRadioNetwork_no_remaining_rab, + }; - LOGP(DRANAP, LOGL_INFO, "Received Iu Release Request, Sending Release Command\n"); - msg = ranap_new_msg_iu_rel_cmd(&ies->cause); + if (!cause) + cause = &default_cause; + + msg = ranap_new_msg_iu_rel_cmd(cause); msg->l2h = msg->data; prim = (struct osmo_scu_prim *) msgb_push(msg, sizeof(*prim)); prim->u.data.conn_id = ctx->conn_id; osmo_prim_init(&prim->oph, SCCP_SAP_USER, OSMO_SCU_PRIM_N_DATA, PRIM_OP_REQUEST, msg); - osmo_sua_user_link_down(ctx->link, &prim->oph); + return osmo_sua_user_link_down(ctx->link, &prim->oph); +} + +static int ranap_handle_co_iu_rel_req(struct ue_conn_ctx *ctx, RANAP_Iu_ReleaseRequestIEs_t *ies) +{ + LOGP(DRANAP, LOGL_INFO, "Received Iu Release Request, Sending Release Command\n"); + iu_tx_release(ctx, &ies->cause); return 0; } @@ -413,6 +455,7 @@ static int ranap_handle_co_rab_ass_resp(struct ue_conn_ctx *ctx, RANAP_RAB_Assig ranap_free_rab_setupormodifieditemies(&setup_ies); } + /* FIXME: handle RAB Ass failure? */ return rc; } diff --git a/src/libiu/iu_vty.c b/src/libiu/iu_vty.c index 91eed96be..73ad126ba 100644 --- a/src/libiu/iu_vty.c +++ b/src/libiu/iu_vty.c @@ -18,33 +18,91 @@ */ #include +#include +#include #include #include -/* Pointer to the actual asn_debug value as passed from main scopes. */ -static int *g_asn_debug_p = NULL; +#include + +static enum nsap_addr_enc *g_rab_assign_addr_enc = NULL; DEFUN(logging_asn_debug, logging_asn_debug_cmd, "logging asn1-debug (1|0)", LOGGING_STR + "Log ASN.1 debug messages to stderr\n" + "Log ASN.1 debug messages to stderr\n" + "Do not log ASN.1 debug messages to stderr\n") +{ + asn_debug = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(logging_asn_xer_print, + logging_asn_xer_print_cmd, + "logging asn1-xer-print (1|0)", + LOGGING_STR "Log human readable representations of all ASN.1 messages to stderr\n" "Log decoded ASN.1 messages to stderr\n" "Do not log decoded ASN.1 messages to stderr\n") { - if (!g_asn_debug_p) { - vty_out(vty, "%%ASN.1 debugging not available%s", VTY_NEWLINE); + asn1_xer_print = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_iu_rab_assign_addr_enc, cfg_iu_rab_assign_addr_enc_cmd, + "iu rab-assign-addr-enc (x213|v4raw)", + "Iu interface protocol options\n" + "Choose RAB Assignment's Transport Layer Address encoding\n" + "ITU-T X.213 compliant address encoding (default)\n" + "32bit length raw IPv4 address (for ip.access nano3G)\n") +{ + if (!g_rab_assign_addr_enc) { + vty_out(vty, "%%RAB Assignment Transport Layer Address" + " encoding not available%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(argv[0], "v4raw") == 0) + *g_rab_assign_addr_enc = NSAP_ADDR_ENC_V4RAW; + else + *g_rab_assign_addr_enc = NSAP_ADDR_ENC_X213; + return CMD_SUCCESS; +} + +int iu_vty_config_write(struct vty *vty, const char *indent) +{ + if (!g_rab_assign_addr_enc) { + vty_out(vty, "%%RAB Assignment Transport Layer Address" + " encoding not available%s", VTY_NEWLINE); + return CMD_WARNING; + } + + switch (*g_rab_assign_addr_enc) { + case NSAP_ADDR_ENC_V4RAW: + vty_out(vty, "%siu rab-assign-addr-enc v4raw%s", indent, + VTY_NEWLINE); + break; + case NSAP_ADDR_ENC_X213: + /* default value, no need to write anything */ + break; + default: + LOGP(0, LOGL_ERROR, "Invalid value for" + " net.iu.rab_assign_addr_enc: %d\n", + *g_rab_assign_addr_enc); return CMD_WARNING; } - *g_asn_debug_p = atoi(argv[0]); return CMD_SUCCESS; } -void iu_vty_init(int *asn_debug_p) +void iu_vty_init(int iu_parent_node, enum nsap_addr_enc *rab_assign_addr_enc) { - g_asn_debug_p = asn_debug_p; + g_rab_assign_addr_enc = rab_assign_addr_enc; install_element(CFG_LOG_NODE, &logging_asn_debug_cmd); + install_element(CFG_LOG_NODE, &logging_asn_xer_print_cmd); + install_element(iu_parent_node, &cfg_iu_rab_assign_addr_enc_cmd); } diff --git a/src/libmgcp/Makefile.am b/src/libmgcp/Makefile.am index 5faf6027a..5d7844da4 100644 --- a/src/libmgcp/Makefile.am +++ b/src/libmgcp/Makefile.am @@ -30,11 +30,14 @@ noinst_HEADERS = \ $(NULL) libmgcp_a_SOURCES = \ + mgcp_common.c \ mgcp_protocol.c \ mgcp_network.c \ mgcp_vty.c \ mgcp_osmux.c \ mgcp_sdp.c \ + mgcpgw_client.c \ + mgcpgw_client_vty.c \ $(NULL) if BUILD_MGCP_TRANSCODING libmgcp_a_SOURCES += \ diff --git a/src/libmgcp/mgcp_common.c b/src/libmgcp/mgcp_common.c new file mode 100644 index 000000000..43c866768 --- /dev/null +++ b/src/libmgcp/mgcp_common.c @@ -0,0 +1,54 @@ +/* Media Gateway Control Protocol Media Gateway: RFC 3435 */ +/* Implementations useful both for the MGCP GW as well as MGCP GW clients */ + +/* + * (C) 2016 by sysmocom s.m.f.c. GmbH + * 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 . + * + */ + +#include + +#include +#include + +const struct value_string mgcp_connection_mode_strs[] = { + { MGCP_CONN_NONE, "none" }, + { MGCP_CONN_RECV_SEND, "sendrecv" }, + { MGCP_CONN_SEND_ONLY, "sendonly" }, + { MGCP_CONN_RECV_ONLY, "recvonly" }, + { MGCP_CONN_LOOPBACK, "loopback" }, + { 0, NULL } +}; + +/* Ensure that the msg->l2h is NUL terminated. */ +int mgcp_msg_terminate_nul(struct msgb *msg) +{ + unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */ + if (tail[-1] == '\0') + /* nothing to do */; + else if (msgb_tailroom(msg) > 0) + tail[0] = '\0'; + else if (tail[-1] == '\r' || tail[-1] == '\n') + tail[-1] = '\0'; + else { + LOGP(DMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: " + "Length: %d, Buffer size: %d\n", + msgb_l2len(msg), msg->data_len); + return -ENOTSUP; + } + return 0; +} diff --git a/src/libmgcp/mgcp_network.c b/src/libmgcp/mgcp_network.c index abce6e49d..c9fe17973 100644 --- a/src/libmgcp/mgcp_network.c +++ b/src/libmgcp/mgcp_network.c @@ -537,7 +537,11 @@ void mgcp_patch_and_count(struct mgcp_endpoint *endp, struct mgcp_rtp_state *sta if (payload < 0) return; +#if 0 + DEBUGP(DMGCP, "Payload hdr payload %u -> endp payload %u\n", + rtp_hdr->payload_type, payload); rtp_hdr->payload_type = payload; +#endif } /* @@ -588,6 +592,14 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, struct mgcp_rtp_state *rtp_state; int tap_idx; + LOGP(DMGCP, LOGL_DEBUG, + "endpoint %x dest %s tcfg->audio_loop %d endp->conn_mode %d (== loopback: %d)\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + tcfg->audio_loop, + endp->conn_mode, + endp->conn_mode == MGCP_CONN_LOOPBACK); + /* For loop toggle the destination and then dispatch. */ if (tcfg->audio_loop) dest = !dest; @@ -605,10 +617,35 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, rtp_state = &endp->net_state; tap_idx = MGCP_TAP_BTS_OUT; } + LOGP(DMGCP, LOGL_DEBUG, + "endpoint %x dest %s net_end %s %d %d bts_end %s %d %d rtp_end %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + + inet_ntoa(endp->net_end.addr), + ntohs(endp->net_end.rtp_port), + ntohs(endp->net_end.rtcp_port), + + inet_ntoa(endp->bts_end.addr), + ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port), - if (!rtp_end->output_enabled) + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + + if (!rtp_end->output_enabled) { rtp_end->dropped_packets += 1; - else if (is_rtp) { + LOGP(DMGCP, LOGL_DEBUG, + "endpoint %x output disabled, drop to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + } else if (is_rtp) { int cont; int nbytes = 0; int len = rc; @@ -619,8 +656,17 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, break; mgcp_patch_and_count(endp, rtp_state, rtp_end, addr, buf, len); + LOGP(DMGCP, LOGL_DEBUG, + "endpoint %x process/send to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + (dest == MGCP_DEST_NET)? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); forward_data(rtp_end->rtp.fd, &endp->taps[tap_idx], buf, len); + rc = mgcp_udp_send(rtp_end->rtp.fd, &rtp_end->addr, rtp_end->rtp_port, buf, len); @@ -632,6 +678,15 @@ int mgcp_send(struct mgcp_endpoint *endp, int dest, int is_rtp, } while (len > 0); return nbytes; } else if (!tcfg->omit_rtcp) { + LOGP(DMGCP, LOGL_DEBUG, + "endpoint %x send to %s %s %d %d\n", + ENDPOINT_NUMBER(endp), + dest == MGCP_DEST_NET? "net" : "bts", + inet_ntoa(rtp_end->addr), + ntohs(rtp_end->rtp_port), + ntohs(rtp_end->rtcp_port) + ); + return mgcp_udp_send(rtp_end->rtcp.fd, &rtp_end->addr, rtp_end->rtcp_port, buf, rc); @@ -676,9 +731,28 @@ static int rtp_data_net(struct osmo_fd *fd, unsigned int what) if (rc <= 0) return -1; + LOGP(DMGCP, LOGL_DEBUG, + "endpoint %x", + ENDPOINT_NUMBER(endp)); + LOGPC(DMGCP, LOGL_DEBUG, + " from net %s %d", + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + LOGPC(DMGCP, LOGL_DEBUG, + " net_end %s %d %d", + inet_ntoa(endp->net_end.addr), + ntohs(endp->net_end.rtp_port), + ntohs(endp->net_end.rtcp_port)); + LOGPC(DMGCP, LOGL_DEBUG, + " bts_end %s %d %d\n", + inet_ntoa(endp->bts_end.addr), + ntohs(endp->bts_end.rtp_port), + ntohs(endp->bts_end.rtcp_port) + ); + if (memcmp(&addr.sin_addr, &endp->net_end.addr, sizeof(addr.sin_addr)) != 0) { LOGP(DMGCP, LOGL_ERROR, - "Endpoint 0x%x data from wrong address %s vs. ", + "rtp_data_net: Endpoint 0x%x data from wrong address %s vs. ", ENDPOINT_NUMBER(endp), inet_ntoa(addr.sin_addr)); LOGPC(DMGCP, LOGL_ERROR, "%s\n", inet_ntoa(endp->net_end.addr)); @@ -691,7 +765,7 @@ static int rtp_data_net(struct osmo_fd *fd, unsigned int what) if (endp->net_end.rtp_port != addr.sin_port && endp->net_end.rtcp_port != addr.sin_port) { LOGP(DMGCP, LOGL_ERROR, - "Data from wrong source port %d on 0x%x\n", + "rtp_data_net: Data from wrong source port %d on 0x%x\n", ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); return -1; } @@ -701,6 +775,12 @@ static int rtp_data_net(struct osmo_fd *fd, unsigned int what) break; } + LOGP(DMGCP, LOGL_DEBUG, + "rtp_data_net: Endpoint %x data from %s %d\n", + ENDPOINT_NUMBER(endp), + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + /* throw away the dummy message */ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from network on 0x%x\n", @@ -780,7 +860,7 @@ static int rtp_data_bts(struct osmo_fd *fd, unsigned int what) if (memcmp(&endp->bts_end.addr, &addr.sin_addr, sizeof(addr.sin_addr)) != 0) { LOGP(DMGCP, LOGL_ERROR, - "Data from wrong bts %s on 0x%x\n", + "rtp_data_bts: Data from wrong bts %s on 0x%x\n", inet_ntoa(addr.sin_addr), ENDPOINT_NUMBER(endp)); return -1; } @@ -788,11 +868,17 @@ static int rtp_data_bts(struct osmo_fd *fd, unsigned int what) if (endp->bts_end.rtp_port != addr.sin_port && endp->bts_end.rtcp_port != addr.sin_port) { LOGP(DMGCP, LOGL_ERROR, - "Data from wrong bts source port %d on 0x%x\n", + "rtp_data_bts: ata from wrong bts source port %d on 0x%x\n", ntohs(addr.sin_port), ENDPOINT_NUMBER(endp)); return -1; } + LOGP(DMGCP, LOGL_DEBUG, + "rtp_data_bts: Endpoint %x data from %s %d\n", + ENDPOINT_NUMBER(endp), + inet_ntoa(addr.sin_addr), + ntohs(addr.sin_port)); + /* throw away the dummy message */ if (rc == 1 && buf[0] == MGCP_DUMMY_LOAD) { LOGP(DMGCP, LOGL_NOTICE, "Filtered dummy from bts on 0x%x\n", @@ -808,6 +894,9 @@ static int rtp_data_bts(struct osmo_fd *fd, unsigned int what) switch (endp->type) { case MGCP_RTP_DEFAULT: + LOGP(DMGCP, LOGL_DEBUG, + "rtp_data_bts: Endpoint %x MGCP_RTP_DEFAULT\n", + ENDPOINT_NUMBER(endp)); return mgcp_send(endp, MGCP_DEST_NET, proto == MGCP_PROTO_RTP, &addr, buf, rc); case MGCP_RTP_TRANSCODED: diff --git a/src/libmgcp/mgcp_protocol.c b/src/libmgcp/mgcp_protocol.c index 4fcadd949..78e41f193 100644 --- a/src/libmgcp/mgcp_protocol.c +++ b/src/libmgcp/mgcp_protocol.c @@ -318,26 +318,14 @@ struct msgb *mgcp_handle_message(struct mgcp_config *cfg, struct msgb *msg) int i, code, handled = 0; struct msgb *resp = NULL; char *data; - unsigned char *tail = msg->l2h + msgb_l2len(msg); /* char after l2 data */ if (msgb_l2len(msg) < 4) { LOGP(DMGCP, LOGL_ERROR, "msg too short: %d\n", msg->len); return NULL; } - /* Ensure that the msg->l2h is NUL terminated. */ - if (tail[-1] == '\0') - /* nothing to do */; - else if (msgb_tailroom(msg) > 0) - tail[0] = '\0'; - else if (tail[-1] == '\r' || tail[-1] == '\n') - tail[-1] = '\0'; - else { - LOGP(DMGCP, LOGL_ERROR, "Cannot NUL terminate MGCP message: " - "Length: %d, Buffer size: %d\n", - msgb_l2len(msg), msg->data_len); + if (mgcp_msg_terminate_nul(msg)) return NULL; - } /* attempt to treat it as a response */ if (sscanf((const char *)&msg->l2h[0], "%3d %*s", &code) == 1) { @@ -547,6 +535,11 @@ static int parse_conn_mode(const char *msg, struct mgcp_endpoint *endp) endp->bts_end.output_enabled = endp->conn_mode & MGCP_CONN_RECV_ONLY ? 1 : 0; + LOGP(DMGCP, LOGL_DEBUG, "endpoint %x connection mode '%s' %d output_enabled net %d bts %d\n", + ENDPOINT_NUMBER(endp), + msg, endp->conn_mode, endp->net_end.output_enabled, + endp->bts_end.output_enabled); + return ret; } @@ -972,6 +965,8 @@ static struct msgb *handle_modify_con(struct mgcp_parse_data *p) break; case MGCP_POLICY_DEFER: /* stop processing */ + LOGP(DMGCP, LOGL_DEBUG, "endp %x MDCX defer\n", + ENDPOINT_NUMBER(endp)); return NULL; break; case MGCP_POLICY_CONT: @@ -1003,6 +998,8 @@ error3: out_silent: + LOGP(DMGCP, LOGL_DEBUG, "endp %x Modify endpoint: silent exit\n", + ENDPOINT_NUMBER(endp)); return NULL; } diff --git a/src/libmgcp/mgcpgw_client.c b/src/libmgcp/mgcpgw_client.c new file mode 100644 index 000000000..9f0c84de2 --- /dev/null +++ b/src/libmgcp/mgcpgw_client.c @@ -0,0 +1,549 @@ +/* mgcp_utils - common functions to setup an MGCP connection + */ +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * 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 . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +struct mgcpgw_client { + struct mgcpgw_client_conf actual; + uint32_t remote_addr; + struct osmo_wqueue wq; + mgcp_trans_id_t next_trans_id; + uint16_t next_endpoint; + struct llist_head responses_pending; +}; + +void mgcpgw_client_conf_init(struct mgcpgw_client_conf *conf) +{ + /* NULL and -1 default to MGCPGW_CLIENT_*_DEFAULT values */ + *conf = (struct mgcpgw_client_conf){ + .local_addr = NULL, + .local_port = -1, + .remote_addr = NULL, + .remote_port = -1, + }; +} + +unsigned int mgcpgw_client_next_endpoint(struct mgcpgw_client *client) +{ + return client->next_endpoint ++; +} + +static void mgcpgw_client_handle_response(struct mgcpgw_client *mgcp, + struct mgcp_response_pending *pending, + struct mgcp_response *response) +{ + if (!pending) { + LOGP(DMGCP, LOGL_ERROR, + "Cannot handle NULL response\n"); + return; + } + if (pending->response_cb) + pending->response_cb(response, pending->priv); + else + LOGP(DMGCP, LOGL_INFO, "MGCP response ignored (NULL cb)\n"); + talloc_free(pending); +} + +static int mgcp_response_parse_head(struct mgcp_response *r, struct msgb *msg) +{ + int comment_pos; + char *end; + + if (mgcp_msg_terminate_nul(msg)) + goto response_parse_failure; + + r->body = (char *)msg->data; + + if (sscanf(r->body, "%3d %u %n", + &r->head.response_code, &r->head.trans_id, + &comment_pos) != 2) + goto response_parse_failure; + + r->head.comment = r->body + comment_pos; + end = strchr(r->head.comment, '\r'); + if (!end) + goto response_parse_failure; + /* Mark the end of the comment */ + *end = '\0'; + r->body = end + 1; + if (r->body[0] == '\n') + r->body ++; + return 0; + +response_parse_failure: + LOGP(DMGCP, LOGL_ERROR, + "Failed to parse MGCP response header\n"); + return -EINVAL; +} + +/* TODO undup against mgcp_protocol.c:mgcp_check_param() */ +static bool mgcp_line_is_valid(const char *line) +{ + const size_t line_len = strlen(line); + if (line[0] == '\0') + return true; + + if (line_len < 2 + || line[1] != '=') { + LOGP(DMGCP, LOGL_ERROR, + "Wrong MGCP option format: '%s'\n", + line); + return false; + } + + return true; +} + +/* Parse a line like "m=audio 16002 RTP/AVP 98" */ +static int mgcp_parse_audio(struct mgcp_response *r, const char *line) +{ + if (sscanf(line, "m=audio %hu", + &r->audio_port) != 1) + goto response_parse_failure; + + return 0; + +response_parse_failure: + LOGP(DMGCP, LOGL_ERROR, + "Failed to parse MGCP response header\n"); + return -EINVAL; +} + +int mgcp_response_parse_params(struct mgcp_response *r) +{ + char *line; + int rc; + OSMO_ASSERT(r->body); + char *data = strstr(r->body, "\n\n"); + + if (!data) { + LOGP(DMGCP, LOGL_ERROR, + "MGCP response: cannot find start of parameters\n"); + return -EINVAL; + } + + /* Advance to after the \n\n, replace the second \n with \0. That's + * where the parameters start. */ + data ++; + *data = '\0'; + data ++; + + for_each_line(line, data) { + if (!mgcp_line_is_valid(line)) + return -EINVAL; + + switch (line[0]) { + case 'm': + rc = mgcp_parse_audio(r, line); + if (rc) + return rc; + break; + default: + /* skip unhandled parameters */ + break; + } + } + return 0; +} + +static struct mgcp_response_pending *mgcpgw_client_response_pending_get( + struct mgcpgw_client *mgcp, + struct mgcp_response *r) +{ + struct mgcp_response_pending *pending; + if (!r) + return NULL; + llist_for_each_entry(pending, &mgcp->responses_pending, entry) { + if (pending->trans_id == r->head.trans_id) { + llist_del(&pending->entry); + return pending; + } + } + return NULL; +} + +/* Feed an MGCP message into the receive processing. + * Parse the head and call any callback registered for the transaction id found + * in the MGCP message. This is normally called directly from the internal + * mgcp_do_read that reads from the socket connected to the MGCP gateway. This + * function is published mainly to be able to feed data from the test suite. + */ +int mgcpgw_client_rx(struct mgcpgw_client *mgcp, struct msgb *msg) +{ + struct mgcp_response r = { 0 }; + struct mgcp_response_pending *pending; + int rc; + + rc = mgcp_response_parse_head(&r, msg); + if (rc) { + LOGP(DMGCP, LOGL_ERROR, "Cannot parse MGCP response\n"); + return -1; + } + + pending = mgcpgw_client_response_pending_get(mgcp, &r); + if (!pending) { + LOGP(DMGCP, LOGL_ERROR, + "Cannot find matching MGCP transaction for trans_id %d\n", + r.head.trans_id); + return -1; + } + + mgcpgw_client_handle_response(mgcp, pending, &r); + return 0; +} + +static int mgcp_do_read(struct osmo_fd *fd) +{ + struct mgcpgw_client *mgcp = fd->data; + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(4096, 128, "mgcp_from_gw"); + if (!msg) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n"); + return -1; + } + + ret = read(fd->fd, msg->data, 4096 - 128); + if (ret <= 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno)); + msgb_free(msg); + return -1; + } else if (ret > 4096 - 128) { + LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret); + msgb_free(msg); + return -1; + } + + msg->l2h = msgb_put(msg, ret); + ret = mgcpgw_client_rx(mgcp, msg); + talloc_free(msg); + return ret; +} + +static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg) +{ + int ret; + static char strbuf[4096]; + unsigned int l = msg->len < sizeof(strbuf)-1 ? msg->len : sizeof(strbuf)-1; + strncpy(strbuf, (const char*)msg->data, l); + strbuf[l] = '\0'; + DEBUGP(DMGCP, "Tx MGCP msg to MGCP GW: '%s'\n", strbuf); + + LOGP(DMGCP, LOGL_DEBUG, "Sending msg to MGCP GW size: %u\n", msg->len); + + ret = write(fd->fd, msg->data, msg->len); + if (ret != msg->len) + LOGP(DMGCP, LOGL_ERROR, "Failed to forward message to MGCP" + " GW: %s\n", strerror(errno)); + + return ret; +} + +struct mgcpgw_client *mgcpgw_client_init(void *ctx, + struct mgcpgw_client_conf *conf) +{ + struct mgcpgw_client *mgcp; + + mgcp = talloc_zero(ctx, struct mgcpgw_client); + + INIT_LLIST_HEAD(&mgcp->responses_pending); + + mgcp->next_trans_id = 1; + mgcp->next_endpoint = 1; + + mgcp->actual.local_addr = conf->local_addr ? conf->local_addr : + MGCPGW_CLIENT_LOCAL_ADDR_DEFAULT; + mgcp->actual.local_port = conf->local_port >= 0 ? (uint16_t)conf->local_port : + MGCPGW_CLIENT_LOCAL_PORT_DEFAULT; + + mgcp->actual.remote_addr = conf->remote_addr ? conf->remote_addr : + MGCPGW_CLIENT_REMOTE_ADDR_DEFAULT; + mgcp->actual.remote_port = conf->remote_port >= 0 ? (uint16_t)conf->remote_port : + MGCPGW_CLIENT_REMOTE_PORT_DEFAULT; + + return mgcp; +} + +int mgcpgw_client_connect(struct mgcpgw_client *mgcp) +{ + int on; + struct sockaddr_in addr; + struct osmo_wqueue *wq; + int rc; + + if (!mgcp) { + LOGP(DMGCP, LOGL_FATAL, "MGCPGW client not initialized properly\n"); + return -EINVAL; + } + + wq = &mgcp->wq; + + wq->bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (wq->bfd.fd < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno); + return -errno; + } + + on = 1; + if (setsockopt(wq->bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to initialize socket for MGCP GW: %s\n", + strerror(errno)); + rc = -errno; + goto error_close_fd; + } + + /* bind socket */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + inet_aton(mgcp->actual.local_addr, &addr.sin_addr); + addr.sin_port = htons(mgcp->actual.local_port); + if (bind(wq->bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to bind for MGCP GW to %s %u\n", + mgcp->actual.local_addr, mgcp->actual.local_port); + rc = -errno; + goto error_close_fd; + } + + /* connect to the remote */ + inet_aton(mgcp->actual.remote_addr, &addr.sin_addr); + addr.sin_port = htons(mgcp->actual.remote_port); + if (connect(wq->bfd.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, + "Failed to connect to MGCP GW at %s %u: %s\n", + mgcp->actual.remote_addr, mgcp->actual.remote_port, + strerror(errno)); + rc = -errno; + goto error_close_fd; + } + + mgcp->remote_addr = htonl(addr.sin_addr.s_addr); + + osmo_wqueue_init(wq, 10); + wq->bfd.when = BSC_FD_READ; + wq->bfd.data = mgcp; + wq->read_cb = mgcp_do_read; + wq->write_cb = mgcp_do_write; + + if (osmo_fd_register(&wq->bfd) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n"); + rc = -EIO; + goto error_close_fd; + } + LOGP(DMGCP, LOGL_INFO, "MGCP GW connection: %s:%u -> %s:%u\n", + mgcp->actual.local_addr, mgcp->actual.local_port, + mgcp->actual.remote_addr, mgcp->actual.remote_port); + + return 0; +error_close_fd: + close(wq->bfd.fd); + wq->bfd.fd = -1; + return rc; +} + +const char *mgcpgw_client_remote_addr_str(struct mgcpgw_client *mgcp) +{ + return mgcp->actual.remote_addr; +} + +uint16_t mgcpgw_client_remote_port(struct mgcpgw_client *mgcp) +{ + return mgcp->actual.remote_port; +} + +/* Return the MGCP GW binary IPv4 address in network byte order. */ +uint32_t mgcpgw_client_remote_addr_n(struct mgcpgw_client *mgcp) +{ + return mgcp->remote_addr; +} + +struct mgcp_response_pending * mgcpgw_client_pending_add( + struct mgcpgw_client *mgcp, + mgcp_trans_id_t trans_id, + mgcp_response_cb_t response_cb, + void *priv) +{ + struct mgcp_response_pending *pending; + + pending = talloc_zero(mgcp, struct mgcp_response_pending); + pending->trans_id = trans_id; + pending->response_cb = response_cb; + pending->priv = priv; + llist_add_tail(&pending->entry, &mgcp->responses_pending); + + return pending; +} + +/* Send the MGCP message in msg to the MGCP GW and handle a response with + * response_cb. NOTE: the response_cb still needs to call + * mgcp_response_parse_params(response) to get the parsed parameters -- to + * potentially save some CPU cycles, only the head line has been parsed when + * the response_cb is invoked. */ +int mgcpgw_client_tx(struct mgcpgw_client *mgcp, struct msgb *msg, + mgcp_response_cb_t response_cb, void *priv) +{ + struct mgcp_response_pending *pending; + mgcp_trans_id_t trans_id; + int rc; + + trans_id = msg->cb[MSGB_CB_MGCP_TRANS_ID]; + if (!trans_id) { + LOGP(DMGCP, LOGL_ERROR, + "Unset transaction id in mgcp send request\n"); + talloc_free(msg); + return -EINVAL; + } + + pending = mgcpgw_client_pending_add(mgcp, trans_id, response_cb, priv); + + if (msgb_l2len(msg) > 4096) { + LOGP(DMGCP, LOGL_ERROR, + "Cannot send, MGCP message too large: %u\n", + msgb_l2len(msg)); + msgb_free(msg); + rc = -EINVAL; + goto mgcp_tx_error; + } + + rc = osmo_wqueue_enqueue(&mgcp->wq, msg); + if (rc) { + LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW\n"); + msgb_free(msg); + goto mgcp_tx_error; + } else + LOGP(DMGCP, LOGL_INFO, "Queued %u bytes for MGCP GW\n", + msgb_l2len(msg)); + return 0; + +mgcp_tx_error: + /* Pass NULL to response cb to indicate an error */ + mgcpgw_client_handle_response(mgcp, pending, NULL); + return -1; +} + +static struct msgb *mgcp_msg_from_buf(mgcp_trans_id_t trans_id, + const char *buf, int len) +{ + struct msgb *msg; + + if (len > (4096 - 128)) { + LOGP(DMGCP, LOGL_ERROR, "Cannot send to MGCP GW:" + " message too large: %d\n", len); + return NULL; + } + + msg = msgb_alloc_headroom(4096, 128, "MGCP tx"); + OSMO_ASSERT(msg); + + char *dst = (char*)msgb_put(msg, len); + memcpy(dst, buf, len); + msg->l2h = msg->data; + msg->cb[MSGB_CB_MGCP_TRANS_ID] = trans_id; + + return msg; +} + +static struct msgb *mgcp_msg_from_str(mgcp_trans_id_t trans_id, + const char *fmt, ...) +{ + static char compose[4096 - 128]; + va_list ap; + int len; + OSMO_ASSERT(fmt); + + va_start(ap, fmt); + len = vsnprintf(compose, sizeof(compose), fmt, ap); + va_end(ap); + if (len >= sizeof(compose)) { + LOGP(DMGCP, LOGL_ERROR, + "Message too large: trans_id=%u len=%d\n", + trans_id, len); + return NULL; + } + if (len < 1) { + LOGP(DMGCP, LOGL_ERROR, + "Failed to compose message: trans_id=%u len=%d\n", + trans_id, len); + return NULL; + } + return mgcp_msg_from_buf(trans_id, compose, len); +} + +static mgcp_trans_id_t mgcpgw_client_next_trans_id(struct mgcpgw_client *mgcp) +{ + /* avoid zero trans_id to distinguish from unset trans_id */ + if (!mgcp->next_trans_id) + mgcp->next_trans_id ++; + return mgcp->next_trans_id ++; +} + +struct msgb *mgcp_msg_crcx(struct mgcpgw_client *mgcp, + uint16_t rtp_endpoint, unsigned int call_id, + enum mgcp_connection_mode mode) +{ + mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp); + return mgcp_msg_from_str(trans_id, + "CRCX %u %x@mgw MGCP 1.0\r\n" + "C: %x\r\n" + "L: p:20, a:AMR, nt:IN\r\n" + "M: %s\r\n" + , + trans_id, + rtp_endpoint, + call_id, + mgcp_cmode_name(mode)); +} + +struct msgb *mgcp_msg_mdcx(struct mgcpgw_client *mgcp, + uint16_t rtp_endpoint, const char *rtp_conn_addr, + uint16_t rtp_port, enum mgcp_connection_mode mode) + +{ + mgcp_trans_id_t trans_id = mgcpgw_client_next_trans_id(mgcp); + return mgcp_msg_from_str(trans_id, + "MDCX %u %x@mgw MGCP 1.0\r\n" + "M: %s\r\n" + "\r\n" + "c=IN IP4 %s\r\n" + "m=audio %u RTP/AVP 255\r\n" + , + trans_id, + rtp_endpoint, + mgcp_cmode_name(mode), + rtp_conn_addr, + rtp_port); +} diff --git a/src/libmgcp/mgcpgw_client_vty.c b/src/libmgcp/mgcpgw_client_vty.c new file mode 100644 index 000000000..a42ee4e5d --- /dev/null +++ b/src/libmgcp/mgcpgw_client_vty.c @@ -0,0 +1,116 @@ +/* MGCPGW client interface to quagga VTY */ +/* (C) 2016 by sysmocom s.m.f.c. GmbH + * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c) + * (C) 2009 by Harald Welte + * (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 . + * + */ + +#include +#include +#include + +#include + +#include +#include + +#define MGCPGW_STR "MGCP gateway configuration for RTP streams\n" + +struct mgcpgw_client_conf *global_mgcpgw_client_conf = NULL; + +DEFUN(cfg_mgcpgw_local_ip, cfg_mgcpgw_local_ip_cmd, + "mgcpgw local-ip A.B.C.D", + MGCPGW_STR "local bind to connect to MGCP gateway with\n" + "local bind IP address\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->local_addr = + talloc_strdup(gsmnet_from_vty(vty), argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcpgw_local_port, cfg_mgcpgw_local_port_cmd, + "mgcpgw local-port <0-65535>", + MGCPGW_STR "local bind to connect to MGCP gateway with\n" + "local bind port\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->local_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcpgw_remote_ip, cfg_mgcpgw_remote_ip_cmd, + "mgcpgw remote-ip A.B.C.D", + MGCPGW_STR "remote bind to connect to MGCP gateway with\n" + "remote bind IP address\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->remote_addr = + talloc_strdup(gsmnet_from_vty(vty), argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_mgcpgw_remote_port, cfg_mgcpgw_remote_port_cmd, + "mgcpgw remote-port <0-65535>", + MGCPGW_STR "remote bind to connect to MGCP gateway with\n" + "remote bind port\n") +{ + if (!global_mgcpgw_client_conf) + return CMD_ERR_NOTHING_TODO; + global_mgcpgw_client_conf->remote_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +int mgcpgw_client_config_write(struct vty *vty, const char *indent) +{ + const char *addr; + int port; + + addr = global_mgcpgw_client_conf->local_addr; + if (addr) + vty_out(vty, "%smgcpgw local-ip %s%s", indent, addr, + VTY_NEWLINE); + port = global_mgcpgw_client_conf->local_port; + if (port >= 0) + vty_out(vty, "%smgcpgw local-port %u%s", indent, + (uint16_t)port, VTY_NEWLINE); + + addr = global_mgcpgw_client_conf->remote_addr; + if (addr) + vty_out(vty, "%smgcpgw remote-ip %s%s", indent, addr, + VTY_NEWLINE); + port = global_mgcpgw_client_conf->remote_port; + if (port >= 0) + vty_out(vty, "%smgcpgw remote-port %u%s", indent, + (uint16_t)port, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +void mgcpgw_client_vty_init(int node, struct mgcpgw_client_conf *conf) +{ + global_mgcpgw_client_conf = conf; + + install_element(node, &cfg_mgcpgw_local_ip_cmd); + install_element(node, &cfg_mgcpgw_local_port_cmd); + install_element(node, &cfg_mgcpgw_remote_ip_cmd); + install_element(node, &cfg_mgcpgw_remote_port_cmd); +} diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am index 3c0651456..16154ff97 100644 --- a/src/libmsc/Makefile.am +++ b/src/libmsc/Makefile.am @@ -12,6 +12,7 @@ AM_CFLAGS = \ $(COVERAGE_CFLAGS) \ $(LIBCRYPTO_CFLAGS) \ $(LIBSMPP34_CFLAGS) \ + $(LIBASN1C_CFLAGS) \ $(NULL) noinst_HEADERS = \ @@ -25,11 +26,14 @@ noinst_LIBRARIES = \ libmsc_a_SOURCES = \ a_iface.c \ auth.c \ + msc_vty.c \ db.c \ gsm_04_08.c \ gsm_04_11.c \ gsm_04_80.c \ gsm_subscriber.c \ + iucs.c \ + iucs_ranap.c \ mncc.c \ mncc_builtin.c \ mncc_sock.c \ @@ -45,6 +49,11 @@ libmsc_a_SOURCES = \ meas_feed.c \ subscr_conn.c \ $(NULL) +if !BUILD_IU +libmsc_a_SOURCES += \ + iu_dummy.c \ + $(NULL) +endif if BUILD_SMPP noinst_HEADERS += \ diff --git a/src/libmsc/a_iface.c b/src/libmsc/a_iface.c index 1f471f97b..caf9d4b06 100644 --- a/src/libmsc/a_iface.c +++ b/src/libmsc/a_iface.c @@ -35,6 +35,14 @@ int a_tx(struct msgb *msg) return -1; } +int a_page(const char *imsi, uint32_t tmsi, uint16_t lac) +{ + LOGP(DMSC, LOGL_ERROR, "Paging to be sent to BSC, but A-interface" + " not implemented: IMSI %s TMSI 0x%08x LAC %u\n", + imsi, tmsi, lac); + return -1; +} + int msc_gsm0808_tx_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, const uint8_t *key, int len, int include_imeisv) { diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c index 21ffaaad8..3f3f90581 100644 --- a/src/libmsc/gsm_04_08.c +++ b/src/libmsc/gsm_04_08.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "bscconfig.h" @@ -69,6 +70,13 @@ #include #include #include +#include + +#include + +#ifdef BUILD_IU +#include +#endif #include @@ -105,7 +113,7 @@ static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection gh->proto_discr = trans->protocol | (trans->transaction_id << 4); } - return gsm0808_submit_dtap(conn, msg, 0, 0); + return msc_tx_dtap(conn, msg); } int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message) @@ -141,7 +149,7 @@ void gsm0408_clear_all_trans(struct gsm_network *net, int protocol) } /* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */ -int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause) +static int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, uint8_t cause) { struct msgb *msg; @@ -184,12 +192,17 @@ static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, len = gsm48_generate_mid_from_imsi(mi, conn->vsub->imsi); mid = msgb_put(msg, len); memcpy(mid, mi, len); + DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT\n", + vlr_subscr_name(conn->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); + DEBUGP(DMM, "-> %s LOCATION UPDATE ACCEPT (TMSI = 0x%08x)\n", + vlr_subscr_name(conn->vsub), + send_tmsi); } /* TODO: Follow-on proceed */ /* TODO: CTS permission */ @@ -197,7 +210,6 @@ static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, /* TODO: Emergency Number List */ /* TODO: Per-MS T3312 */ - DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n"); return gsm48_conn_sendmsg(msg, conn, NULL); } @@ -257,11 +269,11 @@ int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) 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; struct osmo_location_area_id old_lai, new_lai; struct osmo_fsm_inst *lu_fsm; + bool is_utran; int rc; lu = (struct gsm48_loc_upd_req *) gh->data; @@ -327,16 +339,18 @@ int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg) new_lai.lac = conn->lac; DEBUGP(DMM, "LU/new-LAC: %u/%u\n", old_lai.lac, new_lai.lac); + is_utran = (conn->via_ran == RAN_UTRAN_IU); lu_fsm = vlr_loc_update(conn->conn_fsm, SUBSCR_CONN_E_ACCEPTED, SUBSCR_CONN_E_CN_CLOSE, (void*)&conn_from_lu, net->vlr, conn, vlr_lu_type, tmsi, imsi, &old_lai, &new_lai, - conn->network->authentication_required, - conn->network->a5_encryption, + is_utran || conn->network->authentication_required, + is_utran? VLR_CIPH_A5_3 + : conn->network->a5_encryption, classmark_is_r99(&conn->classmark), - conn->via_ran == RAN_UTRAN_IU, + is_utran, net->vlr->cfg.assign_tmsi); if (!lu_fsm) { DEBUGP(DRR, "%s: Can't start LU FSM\n", mi_string); @@ -629,6 +643,7 @@ int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *ms uint8_t mi_len = *(classmark2 + classmark2_len); uint8_t *mi = (classmark2 + classmark2_len + 1); struct osmo_location_area_id lai; + bool is_utran; int rc; lai.plmn.mcc = conn->network->country_code; @@ -685,16 +700,18 @@ int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *ms return rc; } + is_utran = (conn->via_ran == RAN_UTRAN_IU); vlr_proc_acc_req(conn->conn_fsm, SUBSCR_CONN_E_ACCEPTED, SUBSCR_CONN_E_CN_CLOSE, (void*)&conn_from_cm_service_req, net->vlr, conn, VLR_PR_ARQ_T_CM_SERV_REQ, mi-1, &lai, - conn->network->authentication_required, - conn->network->a5_encryption, + is_utran || conn->network->authentication_required, + is_utran? VLR_CIPH_A5_3 + : conn->network->a5_encryption, classmark_is_r99(&conn->classmark), - conn->via_ran == RAN_UTRAN_IU); + is_utran); return 0; } @@ -1038,6 +1055,7 @@ static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct m char mi_string[GSM48_MI_SIZE]; int rc = 0; struct osmo_location_area_id lai; + bool is_utran; lai.plmn.mcc = conn->network->country_code; lai.plmn.mnc = conn->network->network_code; @@ -1063,18 +1081,20 @@ static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct m memcpy(conn->classmark.classmark2, classmark2_lv+1, *classmark2_lv); conn->classmark.classmark2_len = *classmark2_lv; + is_utran = (conn->via_ran == RAN_UTRAN_IU); vlr_proc_acc_req(conn->conn_fsm, SUBSCR_CONN_E_ACCEPTED, SUBSCR_CONN_E_CN_CLOSE, (void*)&conn_from_paging_resp, net->vlr, conn, VLR_PR_ARQ_T_PAGING_RESP, mi_lv, &lai, - conn->network->authentication_required, - conn->network->a5_encryption, + is_utran || conn->network->authentication_required, + is_utran? VLR_CIPH_A5_3 + : conn->network->a5_encryption, classmark_is_r99(&conn->classmark), - conn->via_ran == RAN_UTRAN_IU); + is_utran); - return rc; + return 0; } static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg) @@ -1365,8 +1385,7 @@ static int tch_bridge(struct gsm_network *net, struct gsm_mncc_bridge *bridge) /* Which subscriber do we want to track trans1 or trans2? */ log_set_context(LOG_CTX_VLR_SUBSCR, trans1->vsub); - /* future: msc_call_bridge(trans1, trans2); */ - return -1; + return msc_call_bridge(trans1, trans2); } static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg) @@ -1694,15 +1713,18 @@ static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg) new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF); + msc_call_assignment(trans); + return mncc_recvmsg(trans->net, trans, MNCC_CALL_CONF_IND, &call_conf); } -static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg) +static int gsm48_cc_tx_call_proc_and_assign(struct gsm_trans *trans, void *arg) { struct gsm_mncc *proceeding = arg; struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CC PROC"); struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + int rc; gh->msg_type = GSM48_MT_CC_CALL_PROC; @@ -1718,7 +1740,11 @@ static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg) if (proceeding->fields & MNCC_F_PROGRESS) gsm48_encode_progress(msg, 0, &proceeding->progress); - return gsm48_conn_sendmsg(msg, trans->conn, trans); + rc = gsm48_conn_sendmsg(msg, trans->conn, trans); + if (rc) + return rc; + + return msc_call_assignment(trans); } static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg) @@ -2555,7 +2581,7 @@ static struct downstate { } downstatelist[] = { /* mobile originating call establishment */ {SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */ - MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc}, + MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc_and_assign}, {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */ MNCC_ALERT_REQ, gsm48_cc_tx_alerting}, {SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */ @@ -2732,15 +2758,15 @@ int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg) trans_free(trans); return 0; } - /* store setup informations until paging was successfull */ + /* store setup information until paging succeeds */ memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc)); /* Request a channel */ trans->paging_request = subscr_request_conn( vsub, - RSL_CHANNEED_TCH_F, setup_trig_pag_evt, - trans); + trans, + "MNCC: establish call"); if (!trans->paging_request) { LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n"); vlr_subscr_put(vsub); @@ -3007,6 +3033,16 @@ int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg) return -EACCES; } + if (conn->vsub && conn->vsub->cs.attached_via_ran != conn->via_ran) { + LOGP(DMM, LOGL_ERROR, + "%s: Illegal situation: RAN type mismatch:" + " attached via %s, received message via %s\n", + vlr_subscr_name(conn->vsub), + ran_type_name(conn->vsub->cs.attached_via_ran), + ran_type_name(conn->via_ran)); + return -EACCES; + } + #if 0 if (silent_call_reroute(conn, msg)) return silent_call_rx(conn, msg); @@ -3090,7 +3126,13 @@ static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause) static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref) { struct gsm_subscriber_connection *conn = msc_conn_ref; - return gsm48_tx_mm_serv_ack(conn); + return msc_gsm48_tx_mm_serv_ack(conn); +} + +static int msc_vlr_tx_common_id(void *msc_conn_ref) +{ + struct gsm_subscriber_connection *conn = msc_conn_ref; + return msc_tx_common_id(conn); } /* VLR asks us to transmit a CM Service Reject */ @@ -3124,7 +3166,7 @@ static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result r break; }; - return gsm48_tx_mm_serv_rej(conn, cause); + return msc_gsm48_tx_mm_serv_rej(conn, cause); } /* VLR asks us to start using ciphering */ @@ -3152,9 +3194,47 @@ static int msc_vlr_set_ciph_mode(void *msc_conn_ref, return -EINVAL; } - /* TODO: MSCSPLIT: don't directly push BSC buttons */ - return gsm0808_cipher_mode(conn, ciph, tuple->vec.kc, 8, - retrieve_imeisv); + switch (conn->via_ran) { + case RAN_GERAN_A: + DEBUGP(DMM, "-> CIPHER MODE COMMAND %s\n", + vlr_subscr_name(conn->vsub)); + return msc_gsm0808_tx_cipher_mode(conn, ciph, tuple->vec.kc, 8, + retrieve_imeisv); + case RAN_UTRAN_IU: +#ifdef BUILD_IU + DEBUGP(DMM, "-> SECURITY MODE CONTROL %s\n", + vlr_subscr_name(conn->vsub)); + return iu_tx_sec_mode_cmd(conn->iu.ue_ctx, tuple, 0, 1); +#else + LOGP(DMM, LOGL_ERROR, "Cannot send Security Mode Control over RAN_UTRAN_IU," + " built without Iu support\n"); + return -ENOTSUP; +#endif + + default: + break; + } + LOGP(DMM, LOGL_ERROR, + "%s: cannot start ciphering, unknown RAN type %d\n", + vlr_subscr_name(conn->vsub), conn->via_ran); + return -ENOTSUP; +} + +void msc_rx_sec_mode_compl(struct gsm_subscriber_connection *conn) +{ + struct vlr_ciph_result vlr_res = {}; + + if (!conn || !conn->vsub) { + LOGP(DMM, LOGL_ERROR, + "Rx Security Mode Complete for invalid conn\n"); + return; + } + + DEBUGP(DMM, "<- SECURITY MODE COMPLETE %s\n", + vlr_subscr_name(conn->vsub)); + + vlr_res.cause = VLR_CIPH_COMPL; + vlr_subscr_rx_ciph_res(conn->vsub, &vlr_res); } /* VLR informs us that the subscriber data has somehow been modified */ @@ -3170,6 +3250,7 @@ static void msc_vlr_subscr_assoc(void *msc_conn_ref, struct gsm_subscriber_connection *conn = msc_conn_ref; OSMO_ASSERT(!conn->vsub); conn->vsub = vlr_subscr_get(vsub); + conn->vsub->cs.attached_via_ran = conn->via_ran; } /* operations that we need to implement for libvlr */ @@ -3182,6 +3263,7 @@ static const struct vlr_ops msc_vlr_ops = { .tx_cm_serv_acc = msc_vlr_tx_cm_serv_acc, .tx_cm_serv_rej = msc_vlr_tx_cm_serv_rej, .set_ciph_mode = msc_vlr_set_ciph_mode, + .tx_common_id = msc_vlr_tx_common_id, .subscr_update = msc_vlr_subscr_update, .subscr_assoc = msc_vlr_subscr_assoc, }; @@ -3203,19 +3285,3 @@ int msc_vlr_start(struct gsm_network *net) return vlr_start("MSC", net->vlr, net->gsup_server_addr_str, net->gsup_server_port); } - -/* This is a temporary shim merely to ensure that the unit tests still work. It - * shall be removed as soon as Iu and A interface paging is implemented. */ -int msc_fake_paging_request(struct vlr_subscr *vsub) -{ - LOGP(DMM, LOGL_ERROR, "Paging currently not implemented in the MSC.\n"); - OSMO_ASSERT(false); -} - -/* This is a temporary shim merely to ensure that the unit tests still work. It - * shall be removed as soon as Iu and A interface paging is implemented. */ -void msc_fake_paging_request_stop(struct vlr_subscr *vsub) -{ - LOGP(DMM, LOGL_ERROR, "Paging currently not implemented in the MSC.\n"); - OSMO_ASSERT(false); -} diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c index 3255a3b6f..bdf2ad7cc 100644 --- a/src/libmsc/gsm_04_11.c +++ b/src/libmsc/gsm_04_11.c @@ -55,7 +55,7 @@ #include #include #include -#include +#include #include #include @@ -128,7 +128,7 @@ static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *m { DEBUGP(DLSMS, "GSM4.11 TX %s\n", osmo_hexdump(msg->data, msg->len)); msg->l3h = msg->data; - return gsm0808_submit_dtap(conn, msg, UM_SAPI_SMS, 1); + return msc_tx_dtap(conn, msg); } /* Prefix msg with a 04.08/04.11 CP header */ @@ -1016,8 +1016,7 @@ int gsm411_send_sms_subscr(struct vlr_subscr *vsub, /* if not, we have to start paging */ LOGP(DLSMS, LOGL_DEBUG, "Sending SMS: no connection open, start paging %s\n", vlr_subscr_name(vsub)); - res = subscr_request_conn(vsub, RSL_CHANNEED_SDCCH, paging_cb_send_sms, - sms); + res = subscr_request_conn(vsub, paging_cb_send_sms, sms, "send SMS"); if (!res) { send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, GSM_PAGING_BUSY); sms_free(sms); diff --git a/src/libmsc/gsm_04_80.c b/src/libmsc/gsm_04_80.c index 479d6fbd2..bec1d26f4 100644 --- a/src/libmsc/gsm_04_80.c +++ b/src/libmsc/gsm_04_80.c @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include @@ -106,7 +106,7 @@ int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn, | (1<<7); /* TI direction = 1 */ gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; - return gsm0808_submit_dtap(conn, msg, 0, 0); + return msc_tx_dtap(conn, msg); } int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, @@ -135,7 +135,7 @@ int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn, gh->proto_discr |= req->transaction_id | (1<<7); /* TI direction = 1 */ gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE; - return gsm0808_submit_dtap(conn, msg, 0, 0); + return msc_tx_dtap(conn, msg); } int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, const char *text) @@ -143,7 +143,7 @@ int msc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, cons struct msgb *msg = gsm0480_create_ussd_notify(level, text); if (!msg) return -1; - return gsm0808_submit_dtap(conn, msg, 0, 0); + return msc_tx_dtap(conn, msg); } int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) @@ -151,5 +151,5 @@ int msc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) struct msgb *msg = gsm0480_create_ussd_release_complete(); if (!msg) return -1; - return gsm0808_submit_dtap(conn, msg, 0, 0); + return msc_tx_dtap(conn, msg); } diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c index 69d79b0c7..ac6c96a88 100644 --- a/src/libmsc/gsm_subscriber.c +++ b/src/libmsc/gsm_subscriber.c @@ -40,6 +40,9 @@ #include #include #include +#include +#include +#include int subscr_paging_dispatch(unsigned int hooknum, unsigned int event, struct msgb *msg, void *data, void *param) @@ -49,27 +52,41 @@ int subscr_paging_dispatch(unsigned int hooknum, unsigned int event, struct vlr_subscr *vsub = param; struct paging_signal_data sig_data; - OSMO_ASSERT(vsub && vsub->cs.is_paging); + OSMO_ASSERT(vsub); + OSMO_ASSERT(hooknum == GSM_HOOK_RR_PAGING); + OSMO_ASSERT(!(conn && (conn->vsub != vsub))); + OSMO_ASSERT(!((event == GSM_PAGING_SUCCEEDED) && !conn)); - /* FIXME: implement stop paging in libmsc; - * faking it for the unit tests to still work */ - msc_fake_paging_request_stop(vsub); + LOGP(DPAG, LOGL_DEBUG, "Paging %s for %s (event=%d)\n", + event == GSM_PAGING_SUCCEEDED ? "success" : "failure", + vlr_subscr_name(vsub), event); + + if (!vsub->cs.is_paging) { + LOGP(DPAG, LOGL_ERROR, + "Paging Response received for subscriber" + " that is not paging.\n"); + return -EINVAL; + } + + if (event == GSM_PAGING_SUCCEEDED) + msc_stop_paging(vsub); /* Inform parts of the system we don't know */ - sig_data.vsub = vsub; - sig_data.bts = conn ? conn->bts : NULL; - sig_data.conn = conn; + sig_data.vsub = vsub; + sig_data.conn = conn; sig_data.paging_result = event; - osmo_signal_dispatch( - SS_PAGING, - event == GSM_PAGING_SUCCEEDED ? - S_PAGING_SUCCEEDED : S_PAGING_EXPIRED, - &sig_data - ); + osmo_signal_dispatch(SS_PAGING, + event == GSM_PAGING_SUCCEEDED ? + S_PAGING_SUCCEEDED : S_PAGING_EXPIRED, + &sig_data); llist_for_each_entry_safe(request, tmp, &vsub->cs.requests, entry) { llist_del(&request->entry); - request->cbfn(hooknum, event, msg, data, request->param); + if (request->cbfn) { + LOGP(DPAG, LOGL_DEBUG, "Calling paging cbfn.\n"); + request->cbfn(hooknum, event, msg, data, request->param); + } else + LOGP(DPAG, LOGL_DEBUG, "Paging without action.\n"); talloc_free(request); } @@ -79,21 +96,48 @@ int subscr_paging_dispatch(unsigned int hooknum, unsigned int event, return 0; } -struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub, int channel_type, gsm_cbfn *cbfn, - void *param) +int msc_paging_request(struct vlr_subscr *vsub) +{ + /* The subscriber was last seen in subscr->lac. Find out which + * BSCs/RNCs are responsible and send them a paging request via open + * SCCP connections (if any). */ + /* TODO Implementing only RNC paging, since this is code on the iu branch. + * Need to add BSC paging at some point. */ + switch (vsub->cs.attached_via_ran) { + case RAN_GERAN_A: + return a_page(vsub->imsi, vsub->tmsi, vsub->lac); + case RAN_UTRAN_IU: + return iu_page_cs(vsub->imsi, + vsub->tmsi == GSM_RESERVED_TMSI? + NULL : &vsub->tmsi, + vsub->lac); + default: + break; + } + + LOGP(DPAG, LOGL_ERROR, "%s: Cannot page, subscriber not attached\n", + vlr_subscr_name(vsub)); + return -EINVAL; +} + +/*! \brief Start a paging request for vsub, call cbfn(param) when done. + * \param vsub subscriber to page. + * \param cbfn function to call when the conn is established. + * \param param caller defined param to pass to cbfn(). + * \param label human readable label of the request kind used for logging. + */ +struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub, + gsm_cbfn *cbfn, void *param, + const char *label) { int rc; struct subscr_request *request; /* Start paging.. we know it is async so we can do it before */ if (!vsub->cs.is_paging) { - LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet.\n", + LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet, start paging.\n", vlr_subscr_name(vsub)); - - /* FIXME: implement paging in libmsc; - * faking it for the unit tests to still work */ - rc = msc_fake_paging_request(vsub); - + rc = msc_paging_request(vsub); if (rc <= 0) { LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n", vlr_subscr_name(vsub), rc); @@ -102,6 +146,9 @@ struct subscr_request *subscr_request_conn(struct vlr_subscr *vsub, int channel_ /* reduced on the first paging callback */ vlr_subscr_get(vsub); vsub->cs.is_paging = true; + } else { + LOGP(DMM, LOGL_DEBUG, "Subscriber %s already paged.\n", + vlr_subscr_name(vsub)); } /* TODO: Stop paging in case of memory allocation failure */ diff --git a/src/libmsc/iu_dummy.c b/src/libmsc/iu_dummy.c new file mode 100644 index 000000000..1f5dffb55 --- /dev/null +++ b/src/libmsc/iu_dummy.c @@ -0,0 +1,93 @@ +/* Trivial switch-off of external Iu dependencies, + * allowing to run full unit tests even when built without Iu support. */ + +/* + * (C) 2016,2017 by sysmocom s.f.m.c. GmbH + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ + +#include "../../bscconfig.h" +#ifndef BUILD_IU + +#include +#include + +#include +#include +#include + +struct msgb; +struct ue_conn_ctx; +struct gsm_auth_tuple; +struct RANAP_Cause; + +int iu_tx(struct msgb *msg, uint8_t sapi) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_tx() dummy called, NOT transmitting %d bytes: %s\n", + msg->len, osmo_hexdump(msg->data, msg->len)); + return 0; +} + +int iu_tx_sec_mode_cmd(struct ue_conn_ctx *uectx, struct gsm_auth_tuple *tp, + int send_ck) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_sec_mode_cmd() dummy called, NOT transmitting Security Mode Command\n"); + return 0; +} + +int iu_page_cs(const char *imsi, const uint32_t *tmsi, uint16_t lac) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_page_cs() dummy called, NOT paging\n"); + return 23; +} + +int iu_page_ps(const char *imsi, const uint32_t *ptmsi, uint16_t lac, uint8_t rac) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_page_ps() dummy called, NOT paging\n"); + return 0; +} + +struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id, uint32_t rtp_ip, + uint16_t rtp_port, + bool use_x213_nsap) +{ + LOGP(DLGLOBAL, LOGL_INFO, "ranap_new_msg_rab_assign_voice() dummy called, NOT composing RAB Assignment\n"); + return NULL; +} + +int iu_rab_act(struct ue_conn_ctx *ue_ctx, struct msgb *msg) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_rab_act() dummy called, NOT activating RAB\n"); + return 0; +} + +int iu_tx_common_id(struct ue_conn_ctx *uectx, const char *imsi) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_common_id() dummy called, NOT sending CommonID\n"); + return 0; +} + +int iu_tx_release(struct ue_conn_ctx *ctx, const struct RANAP_Cause *cause) +{ + LOGP(DLGLOBAL, LOGL_INFO, "iu_tx_release() dummy called, NOT sending Release\n"); + return 0; +} + +#endif diff --git a/src/libmsc/iucs.c b/src/libmsc/iucs.c new file mode 100644 index 000000000..aeda1406a --- /dev/null +++ b/src/libmsc/iucs.c @@ -0,0 +1,191 @@ +/* Code to manage MSC subscriber connections over IuCS interface */ + +/* + * (C) 2016,2017 by sysmocom s.f.m.c. GmbH + * + * Author: Neels Hofmeyr + * + * 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 . + * + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include + +/* For A-interface see libbsc/bsc_api.c subscr_con_allocate() */ +static struct gsm_subscriber_connection *subscr_conn_allocate_iu(struct gsm_network *network, + struct ue_conn_ctx *ue, + uint16_t lac) +{ + struct gsm_subscriber_connection *conn; + + DEBUGP(DIUCS, "Allocating IuCS subscriber conn: lac %d, link_id %p, conn_id %" PRIx32 "\n", + lac, ue->link, ue->conn_id); + + conn = talloc_zero(network, struct gsm_subscriber_connection); + if (!conn) + return NULL; + + conn->network = network; + conn->via_ran = RAN_UTRAN_IU; + conn->iu.ue_ctx = ue; + conn->iu.ue_ctx->rab_assign_addr_enc = network->iu.rab_assign_addr_enc; + conn->lac = lac; + + llist_add_tail(&conn->entry, &network->subscr_conns); + return conn; +} + +static int same_ue_conn(struct ue_conn_ctx *a, struct ue_conn_ctx *b) +{ + if (a == b) + return 1; + return (a->link == b->link) + && (a->conn_id == b->conn_id); +} + +static inline void log_subscribers(struct gsm_network *network) +{ + if (!log_check_level(DIUCS, LOGL_DEBUG)) + return; + + struct gsm_subscriber_connection *conn; + int i = 0; + llist_for_each_entry(conn, &network->subscr_conns, entry) { + DEBUGP(DIUCS, "%3d: %s", i, vlr_subscr_name(conn->vsub)); + switch (conn->via_ran) { + case RAN_UTRAN_IU: + DEBUGPC(DIUCS, " Iu"); + if (conn->iu.ue_ctx) { + DEBUGPC(DIUCS, " link %p, conn_id %d", + conn->iu.ue_ctx->link, + conn->iu.ue_ctx->conn_id + ); + } + break; + case RAN_GERAN_A: + DEBUGPC(DIUCS, " A"); + /* TODO log A-interface connection details */ + break; + case RAN_UNKNOWN: + DEBUGPC(DIUCS, " ?"); + break; + default: + DEBUGPC(DIUCS, " invalid"); + break; + } + DEBUGPC(DIUCS, "\n"); + i++; + } + DEBUGP(DIUCS, "subscribers registered: %d\n", i); +} + +/* Return an existing IuCS subscriber connection record for the given link and + * connection IDs, or return NULL if not found. */ +struct gsm_subscriber_connection *subscr_conn_lookup_iu( + struct gsm_network *network, + struct ue_conn_ctx *ue) +{ + struct gsm_subscriber_connection *conn; + + DEBUGP(DIUCS, "Looking for IuCS subscriber: link_id %p, conn_id %" PRIx32 "\n", + ue->link, ue->conn_id); + log_subscribers(network); + + llist_for_each_entry(conn, &network->subscr_conns, entry) { + if (conn->via_ran != RAN_UTRAN_IU) + continue; + if (!same_ue_conn(conn->iu.ue_ctx, ue)) + continue; + DEBUGP(DIUCS, "Found IuCS subscriber for link_id %p, conn_id %" PRIx32 "\n", + ue->link, ue->conn_id); + return conn; + } + DEBUGP(DIUCS, "No IuCS subscriber found for link_id %p, conn_id %" PRIx32 "\n", + ue->link, ue->conn_id); + return NULL; +} + +/* Receive MM/CC/... message from IuCS (SCCP user SAP). + * msg->dst must reference a struct ue_conn_ctx, which identifies the peer that + * sent the msg. + * + * For A-interface see libbsc/bsc_api.c gsm0408_rcvmsg(). */ +int gsm0408_rcvmsg_iucs(struct gsm_network *network, struct msgb *msg, + uint16_t *lac) +{ + int rc; + struct ue_conn_ctx *ue_ctx; + struct gsm_subscriber_connection *conn; + + ue_ctx = (struct ue_conn_ctx*)msg->dst; + + /* TODO: are there message types that could allow us to skip this + * search? */ + conn = subscr_conn_lookup_iu(network, ue_ctx); + + if (conn && lac && (conn->lac != *lac)) { + LOGP(DIUCS, LOGL_ERROR, "IuCS subscriber has changed LAC" + " within the same connection, discarding connection:" + " %s from LAC %d to %d\n", + vlr_subscr_name(conn->vsub), conn->lac, *lac); + /* Deallocate conn with previous LAC */ + msc_subscr_conn_close(conn, GSM_CAUSE_INV_MAND_INFO); + /* At this point we could be tolerant and allocate a new + * connection, but changing the LAC within the same connection + * is shifty. Rather cancel everything. */ + return -1; + } + + if (conn) { + /* Make sure we don't receive RR over IuCS; otherwise all + * messages handled by gsm0408_dispatch() are of interest (CC, + * MM, SMS, NS_SS, maybe even MM_GPRS and SM_GPRS). */ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gh->proto_discr & 0x0f; + OSMO_ASSERT(pdisc != GSM48_PDISC_RR); + + msc_dtap(conn, ue_ctx->conn_id, msg); + rc = 0; + } else { + /* allocate a new connection */ + + if (!lac) { + LOGP(DIUCS, LOGL_ERROR, "New IuCS subscriber" + " but no LAC available. Expecting an InitialUE" + " message containing a LAI IE." + " Dropping connection.\n"); + return -1; + } + + conn = subscr_conn_allocate_iu(network, ue_ctx, *lac); + if (!conn) + abort(); + + /* ownership of conn hereby goes to the MSC: */ + rc = msc_compl_l3(conn, msg, 0); + } + + return rc; +} diff --git a/src/libmsc/iucs_ranap.c b/src/libmsc/iucs_ranap.c new file mode 100644 index 000000000..c016474c9 --- /dev/null +++ b/src/libmsc/iucs_ranap.c @@ -0,0 +1,104 @@ +/* Implementation of RANAP messages to/from an MSC via an Iu-CS interface. + * This keeps direct RANAP dependencies out of libmsc. */ + +/* (C) 2016 by sysmocom s.m.f.c. GmbH + * + * 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 . + * + */ + +#include "../../bscconfig.h" + +#ifdef BUILD_IU + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* To continue authorization after a Security Mode Complete */ +int gsm0408_authorize(struct gsm_subscriber_connection *conn); + +static int iucs_rx_rab_assign(struct gsm_subscriber_connection *conn, + RANAP_RAB_SetupOrModifiedItemIEs_t *setup_ies) +{ + uint8_t rab_id; + RANAP_RAB_SetupOrModifiedItem_t *item = &setup_ies->raB_SetupOrModifiedItem; + + rab_id = item->rAB_ID.buf[0]; + + LOGP(DIUCS, LOGL_NOTICE, + "Received RAB assignment event for %s rab_id=%hhd\n", + vlr_subscr_name(conn->vsub), rab_id); + + return 0; +} + +int iucs_rx_sec_mode_compl(struct gsm_subscriber_connection *conn, + RANAP_SecurityModeCompleteIEs_t *ies) +{ + OSMO_ASSERT(conn->via_ran == RAN_UTRAN_IU); + + /* TODO evalute ies */ + + msc_rx_sec_mode_compl(conn); + return 0; +} + +int iucs_rx_ranap_event(struct gsm_network *network, + struct ue_conn_ctx *ue_ctx, int type, void *data) +{ + struct gsm_subscriber_connection *conn; + + conn = subscr_conn_lookup_iu(network, ue_ctx); + + if (!conn) { + LOGP(DRANAP, LOGL_ERROR, "Cannot find subscriber for IU event %u\n", type); + return -1; + } + + switch (type) { + case IU_EVENT_IU_RELEASE: + case IU_EVENT_LINK_INVALIDATED: + LOGP(DIUCS, LOGL_INFO, "IuCS release for %s\n", + vlr_subscr_name(conn->vsub)); + msc_subscr_conn_close(conn, 0); + return 0; + + case IU_EVENT_SECURITY_MODE_COMPLETE: + LOGP(DIUCS, LOGL_INFO, "IuCS security mode complete for %s\n", + vlr_subscr_name(conn->vsub)); + return iucs_rx_sec_mode_compl(conn, + (RANAP_SecurityModeCompleteIEs_t*)data); + case IU_EVENT_RAB_ASSIGN: + return iucs_rx_rab_assign(conn, + (RANAP_RAB_SetupOrModifiedItemIEs_t*)data); + default: + LOGP(DIUCS, LOGL_NOTICE, "Unknown message received:" + " RANAP event: %i\n", type); + return -1; + } +} + +#endif /* BUILD_IU */ diff --git a/src/libmsc/msc_ifaces.c b/src/libmsc/msc_ifaces.c index 001fcbac0..56cbd49d4 100644 --- a/src/libmsc/msc_ifaces.c +++ b/src/libmsc/msc_ifaces.c @@ -23,11 +23,28 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#include "../../bscconfig.h" + +#ifdef BUILD_IU +extern struct msgb *ranap_new_msg_rab_assign_voice(uint8_t rab_id, + uint32_t rtp_ip, + uint16_t rtp_port, + bool use_x213_nsap); +#endif /* BUILD_IU */ static int msc_tx(struct gsm_subscriber_connection *conn, struct msgb *msg) { + DEBUGP(DMSC, "msc_tx %u bytes to %s via %s\n", + msg->len, vlr_subscr_name(conn->vsub), + ran_type_name(conn->via_ran)); switch (conn->via_ran) { - /* FUTURE case RAN_GERAN_A: msg->dst = conn; return a_tx(msg); @@ -35,7 +52,7 @@ static int msc_tx(struct gsm_subscriber_connection *conn, struct msgb *msg) case RAN_UTRAN_IU: msg->dst = conn->iu.ue_ctx; return iu_tx(msg, 0); - */ + default: LOGP(DMSC, LOGL_ERROR, "msc_tx(): conn->via_ran invalid (%d)\n", @@ -61,7 +78,8 @@ int msc_gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn) gh->proto_discr = GSM48_PDISC_MM; gh->msg_type = GSM48_MT_MM_CM_SERV_ACC; - DEBUGP(DMM, "-> CM SERVICE ACCEPT\n"); + DEBUGP(DMM, "-> CM SERVICE ACCEPT %s\n", + vlr_subscr_name(conn->vsub)); return msc_tx_dtap(conn, msg); } @@ -71,6 +89,7 @@ int msc_gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, enum gsm48_reject_value value) { struct msgb *msg; + conn->received_cm_service_request = false; msg = gsm48_create_mm_serv_rej(value); if (!msg) { @@ -82,3 +101,216 @@ int msc_gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, return msc_tx_dtap(conn, msg); } + +int msc_tx_common_id(struct gsm_subscriber_connection *conn) +{ + /* Common ID is only sent over IuCS */ + if (conn->via_ran != RAN_UTRAN_IU) { + LOGP(DMM, LOGL_INFO, + "%s: Asked to transmit Common ID, but skipping" + " because this is not on UTRAN\n", + vlr_subscr_name(conn->vsub)); + return 0; + } + + DEBUGP(DIUCS, "%s: tx CommonID %s\n", + vlr_subscr_name(conn->vsub), conn->vsub->imsi); + return iu_tx_common_id(conn->iu.ue_ctx, conn->vsub->imsi); +} + +#ifdef BUILD_IU +static void iu_rab_act_cs(struct ue_conn_ctx *uectx, uint8_t rab_id, + uint32_t rtp_ip, uint16_t rtp_port) +{ + struct msgb *msg; + bool use_x213_nsap; + uint32_t conn_id = uectx->conn_id; + + use_x213_nsap = (uectx->rab_assign_addr_enc == NSAP_ADDR_ENC_X213); + + LOGP(DIUCS, LOGL_DEBUG, "Assigning RAB: conn_id=%u, rab_id=%d," + " rtp=%x:%u, use_x213_nsap=%d\n", conn_id, rab_id, rtp_ip, + rtp_port, use_x213_nsap); + + msg = ranap_new_msg_rab_assign_voice(rab_id, rtp_ip, rtp_port, + use_x213_nsap); + msg->l2h = msg->data; + + if (iu_rab_act(uectx, msg)) + LOGP(DIUCS, LOGL_ERROR, "Failed to send RAB Assignment:" + " conn_id=%d rab_id=%d rtp=%x:%u\n", + conn_id, rab_id, rtp_ip, rtp_port); +} + +static void mgcp_response_rab_act_cs_crcx(struct mgcp_response *r, void *priv) +{ + struct gsm_trans *trans = priv; + struct gsm_subscriber_connection *conn = trans->conn; + struct ue_conn_ctx *uectx = conn->iu.ue_ctx; + uint32_t rtp_ip; + int rc; + + if (r->head.response_code != 200) { + LOGP(DMGCP, LOGL_ERROR, + "MGCPGW response yields error: %d %s\n", + r->head.response_code, r->head.comment); + goto rab_act_cs_error; + } + + rc = mgcp_response_parse_params(r); + if (rc) { + LOGP(DMGCP, LOGL_ERROR, + "Cannot parse MGCP response, for %s\n", + vlr_subscr_name(trans->vsub)); + goto rab_act_cs_error; + } + + conn->iu.mgcp_rtp_port_cn = r->audio_port; + + rtp_ip = mgcpgw_client_remote_addr_n(conn->network->mgcpgw.client); + iu_rab_act_cs(uectx, conn->iu.rab_id, rtp_ip, + conn->iu.mgcp_rtp_port_ue); + /* use_x213_nsap == 0 for ip.access nano3G */ + +rab_act_cs_error: + /* FIXME abort call, invalidate conn, ... */ + return; +} + +static int conn_iu_rab_act_cs(struct gsm_trans *trans) +{ + struct gsm_subscriber_connection *conn = trans->conn; + struct mgcpgw_client *mgcp = conn->network->mgcpgw.client; + struct msgb *msg; + + /* HACK. where to scope the RAB Id? At the conn / subscriber / + * ue_conn_ctx? */ + static uint8_t next_rab_id = 1; + conn->iu.rab_id = next_rab_id ++; + + conn->iu.mgcp_rtp_endpoint = + mgcpgw_client_next_endpoint(conn->network->mgcpgw.client); + /* HACK: the addresses should be known from CRCX response + * and config. */ + conn->iu.mgcp_rtp_port_ue = 4000 + 2 * conn->iu.mgcp_rtp_endpoint; + + /* Establish the RTP stream first as looping back to the originator. + * The MDCX will patch through to the counterpart. TODO: play a ring + * tone instead. */ + msg = mgcp_msg_crcx(mgcp, conn->iu.mgcp_rtp_endpoint, trans->callref, + MGCP_CONN_LOOPBACK); + return mgcpgw_client_tx(mgcp, msg, mgcp_response_rab_act_cs_crcx, trans); +} +#endif + +int msc_call_assignment(struct gsm_trans *trans) +{ + struct gsm_subscriber_connection *conn = trans->conn; + + switch (conn->via_ran) { + case RAN_GERAN_A: + LOGP(DMSC, LOGL_ERROR, + "msc_call_assignment(): A-interface BSSMAP Assignment" + " Request not yet implemented\n"); + return -ENOTSUP; + + case RAN_UTRAN_IU: +#ifdef BUILD_IU + return conn_iu_rab_act_cs(trans); +#else + LOGP(DMSC, LOGL_ERROR, + "msc_call_assignment(): cannot send RAB Activation, built without Iu support\n"); + return -ENOTSUP; +#endif + + default: + LOGP(DMSC, LOGL_ERROR, + "msc_tx(): conn->via_ran invalid (%d)\n", + conn->via_ran); + return -EINVAL; + } +} + +static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv); + +static void mgcp_bridge(struct gsm_trans *from, struct gsm_trans *to, + enum bridge_state state, + enum mgcp_connection_mode mode) +{ + struct gsm_subscriber_connection *conn1 = from->conn; + struct gsm_subscriber_connection *conn2 = to->conn; + struct mgcpgw_client *mgcp = conn1->network->mgcpgw.client; + const char *ip; + struct msgb *msg; + + OSMO_ASSERT(mgcp); + + from->bridge.peer = to; + from->bridge.state = state; + + /* Loop back to the same MGCP GW */ + ip = mgcpgw_client_remote_addr_str(mgcp); + + msg = mgcp_msg_mdcx(mgcp, + conn1->iu.mgcp_rtp_endpoint, + ip, conn2->iu.mgcp_rtp_port_cn, + mode); + if (mgcpgw_client_tx(mgcp, msg, mgcp_response_bridge_mdcx, from)) + LOGP(DMGCP, LOGL_ERROR, + "Failed to send MDCX message for %s\n", + vlr_subscr_name(from->vsub)); +} + +static void mgcp_response_bridge_mdcx(struct mgcp_response *r, void *priv) +{ + struct gsm_trans *trans = priv; + struct gsm_trans *peer = trans->bridge.peer; + + switch (trans->bridge.state) { + case BRIDGE_STATE_LOOPBACK_PENDING: + trans->bridge.state = BRIDGE_STATE_LOOPBACK_ESTABLISHED; + + switch (peer->bridge.state) { + case BRIDGE_STATE_LOOPBACK_PENDING: + /* Wait until the other is done as well. */ + return; + case BRIDGE_STATE_LOOPBACK_ESTABLISHED: + /* Now that both are in loopback, switch both to + * forwarding. */ + mgcp_bridge(trans, peer, BRIDGE_STATE_BRIDGE_PENDING, + MGCP_CONN_RECV_SEND); + mgcp_bridge(peer, trans, BRIDGE_STATE_BRIDGE_PENDING, + MGCP_CONN_RECV_SEND); + break; + default: + LOGP(DMGCP, LOGL_ERROR, + "Unexpected bridge state: %d for %s\n", + trans->bridge.state, vlr_subscr_name(trans->vsub)); + break; + } + break; + + case BRIDGE_STATE_BRIDGE_PENDING: + trans->bridge.state = BRIDGE_STATE_BRIDGE_ESTABLISHED; + break; + + default: + LOGP(DMGCP, LOGL_ERROR, + "Unexpected bridge state: %d for %s\n", + trans->bridge.state, vlr_subscr_name(trans->vsub)); + break; + } +} + +int msc_call_bridge(struct gsm_trans *trans1, struct gsm_trans *trans2) +{ + /* First setup as loopback and configure the counterparts' endpoints, + * so that when transmission starts the originating addresses are + * already known to be valid. The mgcp callback will continue. */ + mgcp_bridge(trans1, trans2, BRIDGE_STATE_LOOPBACK_PENDING, + MGCP_CONN_LOOPBACK); + mgcp_bridge(trans2, trans1, BRIDGE_STATE_LOOPBACK_PENDING, + MGCP_CONN_LOOPBACK); + + return 0; +} diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c new file mode 100644 index 000000000..82dc7d679 --- /dev/null +++ b/src/libmsc/msc_vty.c @@ -0,0 +1,130 @@ +/* MSC interface to quagga VTY */ +/* (C) 2016 by sysmocom s.m.f.c. GmbH + * Based on OpenBSC interface to quagga VTY (libmsc/vty_interface_layer3.c) + * (C) 2009 by Harald Welte + * (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 . + * + */ + +/* NOTE: I would have liked to call this the MSC_NODE instead of the MSC_NODE, + * but MSC_NODE already exists to configure a remote MSC for osmo-bsc. */ + +#include + +#include + +#include +#include +#include +#include +#include + +static struct cmd_node msc_node = { + MSC_NODE, + "%s(config-msc)# ", + 1, +}; + +DEFUN(cfg_msc, cfg_msc_cmd, + "msc", "Configure MSC options") +{ + vty->node = MSC_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_msc_assign_tmsi, cfg_msc_assign_tmsi_cmd, + "assign-tmsi", + "Assign TMSI during Location Updating.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->vlr->cfg.assign_tmsi = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_msc_no_assign_tmsi, cfg_msc_no_assign_tmsi_cmd, + "no assign-tmsi", + NO_STR "Assign TMSI during Location Updating.\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->vlr->cfg.assign_tmsi = false; + return CMD_SUCCESS; +} + +static int config_write_msc(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + vty_out(vty, "msc%s", VTY_NEWLINE); + vty_out(vty, " %sassign-tmsi%s", + gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE); + + mgcpgw_client_config_write(vty, " "); +#ifdef BUILD_IU + iu_vty_config_write(vty, " "); +#endif + + return CMD_SUCCESS; +} + +static int config_write_net(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + vty_out(vty, "network%s", VTY_NEWLINE); + vty_out(vty, " network country code %u%s", gsmnet->country_code, VTY_NEWLINE); + vty_out(vty, " mobile network code %u%s", gsmnet->network_code, VTY_NEWLINE); + vty_out(vty, " short name %s%s", gsmnet->name_short, VTY_NEWLINE); + vty_out(vty, " long name %s%s", gsmnet->name_long, VTY_NEWLINE); + vty_out(vty, " auth policy %s%s", gsm_auth_policy_name(gsmnet->auth_policy), VTY_NEWLINE); + vty_out(vty, " location updating reject cause %u%s", + gsmnet->reject_cause, VTY_NEWLINE); + vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE); + vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode), + VTY_NEWLINE); + vty_out(vty, " mm info %u%s", gsmnet->send_mm_info, VTY_NEWLINE); + if (gsmnet->tz.override != 0) { + if (gsmnet->tz.dst) + vty_out(vty, " timezone %d %d %d%s", + gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst, + VTY_NEWLINE); + else + 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); + + return CMD_SUCCESS; +} + +void msc_vty_init(struct gsm_network *msc_network) +{ + common_cs_vty_init(msc_network, config_write_net); + + install_element(CONFIG_NODE, &cfg_msc_cmd); + install_node(&msc_node, config_write_msc); + vty_install_default(MSC_NODE); + install_element(MSC_NODE, &cfg_msc_assign_tmsi_cmd); + install_element(MSC_NODE, &cfg_msc_no_assign_tmsi_cmd); + mgcpgw_client_vty_init(MSC_NODE, &msc_network->mgcpgw.conf); +#ifdef BUILD_IU + iu_vty_init(MSC_NODE, &msc_network->iu.rab_assign_addr_enc); +#endif +} diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c index c847b78f1..ddc383612 100644 --- a/src/libmsc/osmo_msc.c +++ b/src/libmsc/osmo_msc.c @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -40,24 +41,6 @@ static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) gsm411_sapi_n_reject(conn); } -static bool keep_conn(struct gsm_subscriber_connection *conn) -{ - /* TODO: what about a silent call? */ - - if (!conn->conn_fsm) { - DEBUGP(DMM, "No conn_fsm, release conn\n"); - return false; - } - - switch (conn->conn_fsm->state) { - case SUBSCR_CONN_S_NEW: - case SUBSCR_CONN_S_ACCEPTED: - return true; - default: - return false; - } -} - static void subscr_conn_bump(struct gsm_subscriber_connection *conn) { if (!conn) @@ -65,39 +48,32 @@ static void subscr_conn_bump(struct gsm_subscriber_connection *conn) if (!conn->conn_fsm) return; if (!(conn->conn_fsm->state == SUBSCR_CONN_S_ACCEPTED - || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING)) + || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING)) { + DEBUGP(DMM, "%s: bump: conn still being established (%s)\n", + vlr_subscr_name(conn->vsub), + osmo_fsm_inst_state_name(conn->conn_fsm)); return; + } osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_BUMP, NULL); } /* receive a Level 3 Complete message and return MSC_CONN_ACCEPT or * MSC_CONN_REJECT */ -static int msc_compl_l3(struct gsm_subscriber_connection *conn, - struct msgb *msg, uint16_t chosen_channel) +int msc_compl_l3(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint16_t chosen_channel) { - /* Ownership of the gsm_subscriber_connection is still a bit mucky - * between libbsc and libmsc. In libmsc, we use ref counting, but not - * in libbsc. This will become simpler with the MSCSPLIT. */ - - /* reserve for the duration of this function */ msc_subscr_conn_get(conn); - gsm0408_dispatch(conn, msg); - if (!keep_conn(conn)) { - DEBUGP(DMM, "compl_l3: Discarding conn\n"); - /* keep the use_count reserved, libbsc will discard. If we - * released the ref count and discarded here, libbsc would - * double-free. And we will not change bsc_api semantics. */ - return MSC_CONN_REJECT; - } - DEBUGP(DMM, "compl_l3: Keeping conn\n"); - /* Bump whether the conn wants to be closed */ subscr_conn_bump(conn); /* If this should be kept, the conn->conn_fsm has placed a use_count */ msc_subscr_conn_put(conn); + + /* Always return acceptance, because even if the conn was not accepted, + * we assumed ownership of it and the caller shall not interfere with + * that. We may even already have discarded the conn. */ return MSC_CONN_ACCEPT; #if 0 @@ -119,7 +95,7 @@ static int msc_compl_l3(struct gsm_subscriber_connection *conn, } /* Receive a DTAP message from BSC */ -static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) +void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) { msc_subscr_conn_get(conn); gsm0408_dispatch(conn, msg); @@ -170,8 +146,8 @@ static void msc_classmark_chg(struct gsm_subscriber_connection *conn, } /* Receive a CIPHERING MODE COMPLETE from BSC */ -static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn, - struct msgb *msg, uint8_t alg_id) +void msc_cipher_mode_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint8_t alg_id) { struct gsm48_hdr *gh = msgb_l3(msg); unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); @@ -289,7 +265,7 @@ static struct bsc_api msc_handler = { .assign_compl = msc_assign_compl, .assign_fail = msc_assign_fail, .classmark_chg = msc_classmark_chg, - .cipher_mode_compl = msc_ciph_m_compl, + .cipher_mode_compl = msc_cipher_mode_compl, .conn_cleanup = msc_subscr_con_cleanup, }; @@ -308,7 +284,10 @@ static void msc_subscr_conn_release_all(struct gsm_subscriber_connection *conn, switch (conn->via_ran) { case RAN_UTRAN_IU: - /* future: iu_tx_release(conn->iu.ue_ctx, NULL); */ + iu_tx_release(conn->iu.ue_ctx, NULL); + /* FIXME: keep the conn until the Iu Release Outcome is + * received from the UE, or a timeout expires. For now, the log + * says "unknown UE" for each release outcome. */ break; case RAN_GERAN_A: /* future: a_iface_tx_clear_cmd(conn); */ @@ -390,8 +369,12 @@ void _msc_subscr_conn_put(struct gsm_subscriber_connection *conn, "%s: MSC conn use - 1 == %u\n", vlr_subscr_name(conn->vsub), conn->use_count); - if (conn->use_count == 0) { - gsm0808_clear(conn); - bsc_subscr_con_free(conn); - } + if (conn->use_count == 0) + msc_subscr_con_free(conn); +} + +void msc_stop_paging(struct vlr_subscr *vsub) +{ + DEBUGP(DPAG, "Paging can stop for %s\n", vlr_subscr_name(vsub)); + /* tell BSCs and RNCs to stop paging? How? */ } diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c index 5fad4f491..7af7a8055 100644 --- a/src/libmsc/silent_call.c +++ b/src/libmsc/silent_call.c @@ -133,7 +133,8 @@ int gsm_silent_call_start(struct vlr_subscr *vsub, void *data, int type) /* FIXME the VTY command allows selecting a silent call channel type. * This doesn't apply to the situation after MSCSPLIT with an * A-interface. */ - req = subscr_request_conn(vsub, type, paging_cb_silent, data); + req = subscr_request_conn(vsub, paging_cb_silent, data, + "establish silent call"); return req != NULL; } diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c index b28a51128..31decc7b3 100644 --- a/src/libmsc/subscr_conn.c +++ b/src/libmsc/subscr_conn.c @@ -30,6 +30,7 @@ #include #include #include +#include #define SUBSCR_CONN_TIMEOUT 5 /* seconds */ @@ -52,8 +53,8 @@ const struct value_string subscr_conn_from_names[] = { { 0, NULL } }; -static void paging_resp(struct gsm_subscriber_connection *conn, - enum gsm_paging_event pe) +static void paging_event(struct gsm_subscriber_connection *conn, + enum gsm_paging_event pe) { subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->vsub); } @@ -85,11 +86,17 @@ void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) case SUBSCR_CONN_E_MO_CLOSE: case SUBSCR_CONN_E_CN_CLOSE: + if (data) + LOGPFSM(fi, "Close event, cause %u\n", + *(uint32_t*)data); + /* will release further below, see + * 'if (fi->state != SUBSCR_CONN_S_ACCEPTED)' */ break; default: - LOGPFSM(fi, "Unexpected event: %d %s\n", - event, osmo_fsm_event_name(fi->fsm, event)); + LOGPFSML(fi, LOGL_ERROR, + "Unexpected event: %d %s\n", event, + osmo_fsm_event_name(fi->fsm, event)); break; } @@ -102,21 +109,24 @@ void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) /* signal paging success or failure in case this was a paging */ if (from == SUBSCR_CONN_FROM_PAGING_RESP) - paging_resp(conn, - success ? GSM_PAGING_SUCCEEDED - : GSM_PAGING_EXPIRED); + paging_event(conn, + success ? GSM_PAGING_SUCCEEDED + : GSM_PAGING_EXPIRED); + + /* FIXME rate counters */ + /*rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]);*/ /* On failure, discard the conn */ if (!success) { /* TODO: on MO_CLOSE or CN_CLOSE, first go to RELEASING and - * await BSC confirmation? */ + * await BSC/RNC confirmation? */ osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); return; } if (from == SUBSCR_CONN_FROM_CM_SERVICE_REQ) { conn->received_cm_service_request = true; - LOGPFSM(fi, "received_cm_service_request = true\n"); + LOGPFSML(fi, LOGL_DEBUG, "received_cm_service_request = true\n"); } osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_BUMP, data); @@ -125,19 +135,37 @@ void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data) static void subscr_conn_fsm_bump(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_trans *trans; - if (conn->silent_call) + if (conn->silent_call) { + LOGPFSML(fi, LOGL_DEBUG, "bump: silent call still active\n"); return; + } - if (conn->received_cm_service_request) + if (conn->received_cm_service_request) { + LOGPFSML(fi, LOGL_DEBUG, "bump: still awaiting first request after a CM Service Request\n"); return; + } - if (conn->vsub && !llist_empty(&conn->vsub->cs.requests)) + if (conn->vsub && !llist_empty(&conn->vsub->cs.requests)) { + struct subscr_request *sr; + if (!log_check_level(fi->fsm->log_subsys, LOGL_DEBUG)) { + llist_for_each_entry(sr, &conn->vsub->cs.requests, entry) { + LOGPFSML(fi, LOGL_DEBUG, "bump: still active: %s\n", + sr->label); + } + } return; + } - if (trans_has_conn(conn)) + if ((trans = trans_has_conn(conn))) { + LOGPFSML(fi, LOGL_DEBUG, + "bump: connection still has active transaction: %s\n", + gsm48_pdisc_name(trans->protocol)); return; + } + LOGPFSML(fi, LOGL_DEBUG, "bump: releasing conn\n"); osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0); } @@ -269,7 +297,7 @@ static struct osmo_fsm subscr_conn_fsm = { .num_states = ARRAY_SIZE(subscr_conn_fsm_states), .allstate_event_mask = 0, .allstate_action = NULL, - .log_subsys = DVLR, + .log_subsys = DMM, .event_names = subscr_conn_fsm_event_names, .cleanup = subscr_conn_fsm_cleanup, .timer_cb = subscr_conn_fsm_timeout, diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c index d157f5469..7289a8f11 100644 --- a/src/libmsc/transaction.c +++ b/src/libmsc/transaction.c @@ -180,15 +180,15 @@ int trans_assign_trans_id(struct gsm_network *net, struct vlr_subscr *vsub, * \param[in] conn Connection to check * \returns 1 in case there is a transaction, 0 otherwise */ -int trans_has_conn(const struct gsm_subscriber_connection *conn) +struct gsm_trans *trans_has_conn(const struct gsm_subscriber_connection *conn) { struct gsm_trans *trans; llist_for_each_entry(trans, &conn->network->trans_list, entry) if (trans->conn == conn) - return 1; + return trans; - return 0; + return NULL; } /*! Free all transactions associated with a connection, presumably when the diff --git a/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c index 0106f91b3..d1bf6b36d 100644 --- a/src/libmsc/vty_interface_layer3.c +++ b/src/libmsc/vty_interface_layer3.c @@ -21,9 +21,8 @@ #include #include #include -#include -#include #include +#include #include #include @@ -474,17 +473,6 @@ DEFUN(subscriber_ussd_notify, return CMD_SUCCESS; } -DEFUN(ena_subscr_delete, - ena_subscr_delete_cmd, - "subscriber " SUBSCR_TYPES " ID delete", - SUBSCR_HELP "Delete subscriber in VLR\n") -{ - vty_out(vty, "%% 'subscriber delete' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - DEFUN(ena_subscr_expire, ena_subscr_expire_cmd, "subscriber " SUBSCR_TYPES " ID expire", @@ -516,43 +504,6 @@ DEFUN(ena_subscr_expire, return CMD_SUCCESS; } -DEFUN(ena_subscr_authorized, - ena_subscr_authorized_cmd, - "subscriber " SUBSCR_TYPES " ID authorized (0|1)", - SUBSCR_HELP "(De-)Authorize subscriber in HLR\n" - "Subscriber should NOT be authorized\n" - "Subscriber should be authorized\n") -{ - vty_out(vty, "%% 'subscriber authorized' is no longer supported.%s" - "%% Authorization is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - -DEFUN(ena_subscr_name, - ena_subscr_name_cmd, - "subscriber " SUBSCR_TYPES " ID name .NAME", - SUBSCR_HELP "Set the name of the subscriber\n" - "Name of the Subscriber\n") -{ - vty_out(vty, "%% 'subscriber name' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - -DEFUN(ena_subscr_extension, - ena_subscr_extension_cmd, - "subscriber " SUBSCR_TYPES " ID extension EXTENSION", - SUBSCR_HELP "Set the extension (phone number) of the subscriber\n" - "Extension (phone number)\n") -{ - vty_out(vty, "%% 'subscriber extension' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - #define A3A8_ALG_TYPES "(none|xor|comp128v1)" #define A3A8_ALG_HELP \ "Use No A3A8 algorithm\n" \ @@ -571,18 +522,6 @@ DEFUN(ena_subscr_a3a8, return CMD_WARNING; } -DEFUN(subscriber_purge, - subscriber_purge_cmd, - "subscriber purge-inactive", - "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n") -{ - /* TODO: does this still have a use with the VLR? */ - vty_out(vty, "%% 'subscriber purge-inactive' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - DEFUN(subscriber_update, subscriber_update_cmd, "subscriber " SUBSCR_TYPES " ID update", @@ -834,7 +773,6 @@ DEFUN(logging_fltr_imsi, "Filter log messages by IMSI\n" "IMSI to be used as filter\n") { struct vlr_subscr *vlr_subscr; - struct bsc_subscr *bsc_subscr; struct gsm_network *gsmnet = gsmnet_from_vty(vty); struct log_target *tgt = osmo_log_vty2tgt(vty); const char *imsi = argv[0]; @@ -843,16 +781,14 @@ DEFUN(logging_fltr_imsi, return CMD_WARNING; vlr_subscr = vlr_subscr_find_by_imsi(gsmnet->vlr, imsi); - bsc_subscr = bsc_subscr_find_by_imsi(gsmnet->bsc_subscribers, imsi); - if (!vlr_subscr && !bsc_subscr) { + if (!vlr_subscr) { vty_out(vty, "%%no subscriber with IMSI(%s)%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } log_set_filter_vlr_subscr(tgt, vlr_subscr); - log_set_filter_bsc_subscr(tgt, bsc_subscr); return CMD_SUCCESS; } @@ -900,81 +836,6 @@ static int config_write_hlr(struct vty *vty) return CMD_SUCCESS; } -static struct cmd_node nitb_node = { - NITB_NODE, - "%s(config-nitb)# ", - 1, -}; - -DEFUN(cfg_nitb, cfg_nitb_cmd, - "nitb", "Configure NITB options") -{ - vty->node = NITB_NODE; - return CMD_SUCCESS; -} - -/* Note: limit on the parameter length is set by internal vty code limitations */ -DEFUN(cfg_nitb_subscr_random, cfg_nitb_subscr_random_cmd, - "subscriber-create-on-demand random <1-9999999999> <2-9999999999>", - "Set random parameters for a new record when a subscriber is first seen.\n" - "Set random parameters for a new record when a subscriber is first seen.\n" - "Minimum for subscriber extension\n""Maximum for subscriber extension\n") -{ - vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - -DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd, - "subscriber-create-on-demand [no-extension]", - "Make a new record when a subscriber is first seen.\n" - "Do not automatically assign extension to created subscribers\n") -{ - vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - -DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd, - "no subscriber-create-on-demand", - NO_STR "Make a new record when a subscriber is first seen.\n") -{ - vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s" - "%% This is now up to osmo-hlr.%s", - VTY_NEWLINE, VTY_NEWLINE); - return CMD_WARNING; -} - -DEFUN(cfg_nitb_assign_tmsi, cfg_nitb_assign_tmsi_cmd, - "assign-tmsi", - "Assign TMSI during Location Updating.\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - gsmnet->vlr->cfg.assign_tmsi = true; - return CMD_SUCCESS; -} - -DEFUN(cfg_nitb_no_assign_tmsi, cfg_nitb_no_assign_tmsi_cmd, - "no assign-tmsi", - NO_STR "Assign TMSI during Location Updating.\n") -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - gsmnet->vlr->cfg.assign_tmsi = false; - return CMD_SUCCESS; -} - -static int config_write_nitb(struct vty *vty) -{ - struct gsm_network *gsmnet = gsmnet_from_vty(vty); - - vty_out(vty, "nitb%s", VTY_NEWLINE); - vty_out(vty, " %sassign-tmsi%s", - gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE); - return CMD_SUCCESS; -} - int bsc_vty_init_extra(void) { osmo_signal_register_handler(SS_SCALL, scall_cbfn, NULL); @@ -995,13 +856,8 @@ int bsc_vty_init_extra(void) install_element_ve(&show_smsqueue_cmd); install_element_ve(&logging_fltr_imsi_cmd); - install_element(ENABLE_NODE, &ena_subscr_delete_cmd); install_element(ENABLE_NODE, &ena_subscr_expire_cmd); - install_element(ENABLE_NODE, &ena_subscr_name_cmd); - install_element(ENABLE_NODE, &ena_subscr_extension_cmd); - install_element(ENABLE_NODE, &ena_subscr_authorized_cmd); install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd); - install_element(ENABLE_NODE, &subscriber_purge_cmd); install_element(ENABLE_NODE, &smsqueue_trigger_cmd); install_element(ENABLE_NODE, &smsqueue_max_cmd); install_element(ENABLE_NODE, &smsqueue_clear_cmd); @@ -1025,13 +881,5 @@ int bsc_vty_init_extra(void) install_element(HLR_NODE, &cfg_hlr_remote_ip_cmd); install_element(HLR_NODE, &cfg_hlr_remote_port_cmd); - install_element(CONFIG_NODE, &cfg_nitb_cmd); - install_node(&nitb_node, config_write_nitb); - install_element(NITB_NODE, &cfg_nitb_subscr_create_cmd); - install_element(NITB_NODE, &cfg_nitb_subscr_random_cmd); - install_element(NITB_NODE, &cfg_nitb_no_subscr_create_cmd); - install_element(NITB_NODE, &cfg_nitb_assign_tmsi_cmd); - install_element(NITB_NODE, &cfg_nitb_no_assign_tmsi_cmd); - return 0; } diff --git a/src/libvlr/vlr.c b/src/libvlr/vlr.c index 0e0d31c26..d95d1b7ec 100644 --- a/src/libvlr/vlr.c +++ b/src/libvlr/vlr.c @@ -961,6 +961,9 @@ struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops) { struct vlr_instance *vlr = talloc_zero(ctx, struct vlr_instance); OSMO_ASSERT(vlr); + + /* Some of these are needed only on UTRAN, but in case the caller wants + * only GERAN, she should just provide dummy callbacks. */ OSMO_ASSERT(ops->tx_auth_req); OSMO_ASSERT(ops->tx_auth_rej); OSMO_ASSERT(ops->tx_id_req); @@ -969,6 +972,7 @@ struct vlr_instance *vlr_alloc(void *ctx, const struct vlr_ops *ops) OSMO_ASSERT(ops->tx_cm_serv_acc); OSMO_ASSERT(ops->tx_cm_serv_rej); OSMO_ASSERT(ops->set_ciph_mode); + OSMO_ASSERT(ops->tx_common_id); OSMO_ASSERT(ops->subscr_update); OSMO_ASSERT(ops->subscr_assoc); diff --git a/src/libvlr/vlr_access_req_fsm.c b/src/libvlr/vlr_access_req_fsm.c index 67fba566f..f9ed0b57d 100644 --- a/src/libvlr/vlr_access_req_fsm.c +++ b/src/libvlr/vlr_access_req_fsm.c @@ -254,6 +254,14 @@ static void _proc_arq_vlr_node2_post_ciph(struct osmo_fsm_inst *fi) 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); + } + vsub->conf_by_radio_contact_ind = true; if (vsub->loc_conf_in_hlr_ind == false) { /* start Update_Location_Child_VLR. WE use @@ -681,6 +689,10 @@ vlr_proc_acc_req(struct osmo_fsm_inst *parent, (ciphering_required? "+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) { diff --git a/src/libvlr/vlr_lu_fsm.c b/src/libvlr/vlr_lu_fsm.c index ae671c1dc..94bea560f 100644 --- a/src/libvlr/vlr_lu_fsm.c +++ b/src/libvlr/vlr_lu_fsm.c @@ -783,6 +783,14 @@ static void vlr_loc_upd_post_ciph(struct osmo_fsm_inst *fi) 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); + } + vsub->conf_by_radio_contact_ind = true; /* Update LAI */ vsub->cgi.lai = lfp->new_lai; @@ -1413,6 +1421,10 @@ vlr_loc_update(struct osmo_fsm_inst *parent, (ciphering_required? "+Ciph" : " (no Ciph)") : ""); + if (is_utran && !authentication_required) + LOGPFSML(fi, LOGL_ERROR, + "Authentication off on UTRAN network. Good luck.\n"); + osmo_fsm_inst_dispatch(fi, VLR_ULA_E_UPDATE_LA, NULL); return fi; diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c index bac5e4717..8c33e2b57 100644 --- a/src/osmo-bsc/osmo_bsc_api.c +++ b/src/osmo-bsc/osmo_bsc_api.c @@ -536,7 +536,7 @@ static struct bsc_api bsc_handler = { .sapi_n_reject = bsc_sapi_n_reject, .cipher_mode_compl = bsc_cipher_mode_compl, .compl_l3 = bsc_compl_l3, - .dtap = bsc_dtap, + .dtap = bsc_dtap, .assign_compl = bsc_assign_compl, .assign_fail = bsc_assign_fail, .clear_request = bsc_clear_request, diff --git a/src/osmo-msc/Makefile.am b/src/osmo-msc/Makefile.am new file mode 100644 index 000000000..bd734d106 --- /dev/null +++ b/src/osmo-msc/Makefile.am @@ -0,0 +1,57 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(COVERAGE_CFLAGS) \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBSMPP34_CFLAGS) \ + $(LIBCRYPTO_CFLAGS) \ + $(LIBOSMORANAP_CFLAGS) \ + $(LIBASN1C_CFLAGS) \ + $(LIBOSMOSIGTRAN_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-msc \ + $(NULL) + +osmo_msc_SOURCES = \ + msc_main.c \ + $(NULL) + +osmo_msc_LDADD = \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libvlr/libvlr.a \ + $(top_builddir)/src/libcommon-cs/libcommon-cs.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(top_builddir)/src/libmgcp/libmgcp.a \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBSMPP34_LIBS) \ + $(LIBCRYPTO_LIBS) \ + $(LIBOSMOSIGTRAN_LIBS) \ + -ldbi \ + $(NULL) +if BUILD_IU +osmo_msc_LDADD += \ + $(top_builddir)/src/libiu/libiu.a \ + $(LIBOSMORANAP_LIBS) \ + $(LIBASN1C_LIBS) \ + $(NULL) +endif diff --git a/src/osmo-msc/msc_main.c b/src/osmo-msc/msc_main.c new file mode 100644 index 000000000..41661590c --- /dev/null +++ b/src/osmo-msc/msc_main.c @@ -0,0 +1,514 @@ +/* OsmoMSC - Circuit-Switched Core Network (MSC+VLR+HLR+SMSC) implementation + */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Based on OsmoNITB: + * (C) 2008-2010 by Harald Welte + * (C) 2009-2012 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 . + * + */ + +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +/* build switches from the configure script */ +#include "../../bscconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +static const char * const osmomsc_copyright = + "OsmoMSC - Osmocom Circuit-Switched Core Network implementation\r\n" + "Copyright (C) 2016 by sysmocom s.f.m.c. GmbH \r\n" + "Based on OsmoNITB:\r\n" + " (C) 2008-2010 by Harald Welte \r\n" + " (C) 2009-2012 by Holger Hans Peter Freyther \r\n" + "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n" + "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +void *tall_msc_ctx = NULL; + +/* satisfy deps from libbsc legacy. + TODO double check these */ +void *tall_fle_ctx = NULL; +void *tall_paging_ctx = NULL; +void *tall_map_ctx = NULL; +void *tall_upq_ctx = NULL; +/* end deps from libbsc legacy. */ + +static struct { + const char *database_name; + const char *config_file; + int daemonize; + const char *mncc_sock_path; + int use_db_counter; +} msc_cmdline_config = { + "sms.db", + "osmo-msc.cfg", + 0, + 0, + 1 +}; + +/* timer to store statistics */ +#define DB_SYNC_INTERVAL 60, 0 +#define EXPIRE_INTERVAL 10, 0 + +static struct osmo_timer_list db_sync_timer; + +static void create_pcap_file(char *file) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + int fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, mode); + + if (fd < 0) { + perror("Failed to open file for pcap"); + return; + } + + e1_set_pcap_fd(fd); +} + +static void print_usage() +{ + printf("Usage: osmo-nitb\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help This text.\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM 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(" -a --authorize-everyone Authorize every new subscriber. Dangerous!\n"); + printf(" -T --timestamp Prefix every log line with a timestamp.\n"); + printf(" -V --version Print the version of OpenBSC.\n"); + printf(" -P --rtp-proxy Enable the RTP Proxy code inside OpenBSC.\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(" -m --mncc-sock Same as `-M /tmp/bsc_mncc' (deprecated).\n"); + printf(" -C --no-dbcounter Disable regular syncing of counters to database.\n"); + printf(" -r --rf-ctl PATH A unix domain socket to listen for cmds.\n"); + printf(" -p --pcap PATH Write abis communication to pcap trace file.\n"); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + 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'}, + {"authorize-everyone", 0, 0, 'a'}, + {"pcap", 1, 0, 'p'}, + {"timestamp", 0, 0, 'T'}, + {"version", 0, 0, 'V' }, + {"rtp-proxy", 0, 0, 'P'}, + {"log-level", 1, 0, 'e'}, + {"mncc-sock", 0, 0, 'm'}, + {"mncc-sock-path", 1, 0, 'M'}, + {"no-dbcounter", 0, 0, 'C'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:Dsl:ap:TPVc:e:mCM:", + long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + msc_cmdline_config.daemonize = 1; + break; + case 'l': + msc_cmdline_config.database_name = optarg; + break; + case 'c': + msc_cmdline_config.config_file = optarg; + break; + case 'p': + create_pcap_file(optarg); + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; +#if BEFORE_MSCSPLIT + case 'P': + ipacc_rtp_direct = 0; + break; +#endif + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'M': + msc_cmdline_config.mncc_sock_path = optarg; + break; + case 'm': + msc_cmdline_config.mncc_sock_path = "/tmp/bsc_mncc"; + break; + case 'C': + msc_cmdline_config.use_db_counter = 0; + break; + case 'V': + print_version(1); + exit(0); + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + } + } +} + +struct gsm_network *msc_network_alloc(void *ctx, + mncc_recv_cb_t mncc_recv) +{ + struct gsm_network *net = gsm_network_init(ctx, 1, 1, mncc_recv); + if (!net) + return NULL; + + net->name_long = talloc_strdup(net, "OsmoMSC"); + net->name_short = talloc_strdup(net, "OsmoMSC"); + + net->gsup_server_addr_str = talloc_strdup(net, + MSC_HLR_REMOTE_IP_DEFAULT); + net->gsup_server_port = MSC_HLR_REMOTE_PORT_DEFAULT; + + mgcpgw_client_conf_init(&net->mgcpgw.conf); + + return net; +} + +void msc_network_shutdown(struct gsm_network *net) +{ + /* nothing here yet */ +} + +static struct gsm_network *msc_network = NULL; + +extern void *tall_vty_ctx; +static void signal_handler(int signal) +{ + fprintf(stdout, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + msc_network_shutdown(msc_network); + osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); + sleep(3); + exit(0); + 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 */ + case SIGUSR1: + talloc_report(tall_vty_ctx, stderr); + talloc_report_full(tall_msc_ctx, stderr); + break; + case SIGUSR2: + talloc_report_full(tall_vty_ctx, stderr); + break; + default: + break; + } +} + +/* timer handling */ +static int _db_store_counter(struct osmo_counter *counter, void *data) +{ + return db_store_counter(counter); +} + +static void db_sync_timer_cb(void *data) +{ + /* store counters to database and re-schedule */ + osmo_counters_for_each(_db_store_counter, NULL); + osmo_timer_schedule(&db_sync_timer, DB_SYNC_INTERVAL); +} + +extern int bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info msc_vty_info = { + .name = "OsmoMSC", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +static int rcvmsg_iu_cs(struct msgb *msg, struct gprs_ra_id *ra_id, + uint16_t *sai) +{ + DEBUGP(DIUCS, "got IuCS message" + " %d bytes: %s\n", + msg->len, osmo_hexdump(msg->data, msg->len)); + if (ra_id) { + DEBUGP(DIUCS, "got IuCS message on" + " MNC %d MCC %d LAC %d RAC %d\n", + ra_id->mnc, ra_id->mcc, ra_id->lac, ra_id->rac); + } + + return gsm0408_rcvmsg_iucs(msc_network, msg, ra_id? &ra_id->lac : NULL); +} + +static int rx_iu_event(struct ue_conn_ctx *ctx, enum iu_event_type type, + void *data) +{ + DEBUGP(DIUCS, "got IuCS event %u: %s\n", type, + iu_event_type_str(type)); + + return iucs_rx_ranap_event(msc_network, ctx, type, data); +} + +int main(int argc, char **argv) +{ + int rc; + + msc_vty_info.copyright = osmomsc_copyright; + + tall_msc_ctx = talloc_named_const(NULL, 1, "osmo_msc"); + talloc_ctx_init(tall_msc_ctx); + + osmo_init_logging(&log_info); + osmo_stats_init(tall_msc_ctx); + + /* For --version, vty_init() must be called before handling options */ + vty_init(&msc_vty_info); + + /* Parse options */ + handle_options(argc, argv); + + /* Allocate global gsm_network struct; choose socket/internal MNCC */ + msc_network = msc_network_alloc(tall_msc_ctx, + msc_cmdline_config.mncc_sock_path? + mncc_sock_from_cc + : int_mncc_recv); + if (!msc_network) + return -ENOMEM; + + if (msc_vlr_alloc(msc_network)) { + fprintf(stderr, "Failed to allocate VLR\n"); + exit(1); + } + + ctrl_vty_init(tall_msc_ctx); + logging_vty_add_cmds(&log_info); + msc_vty_init(msc_network); + bsc_vty_init_extra(); + +#ifdef BUILD_SMPP + if (smpp_openbsc_alloc_init(tall_msc_ctx) < 0) + return -1; +#endif + + /* + * For osmo-nitb, skip TCH/F for now, because otherwise dyn TS + * always imply the possibility to have a mix of TCH/F and + * TCH/H channels; if two phones request a TCH/F and a TCH/H, + * respectively, they cannot call each other. If we deny TCH/F, + * they will both fall back to TCH/H, and dynamic channels are + * usable. See OS#1778. + * + * A third-party MSC may well be able to handle a TCH/H TCH/F + * mismatch. Moreover, this option may be overwritten in the + * config file or in VTY. + */ + msc_network->dyn_ts_allow_tch_f = false; + + rc = vty_read_config_file(msc_cmdline_config.config_file, NULL); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", + msc_cmdline_config.config_file); + return 1; + } + + /* Initialize MNCC socket if appropriate */ + if (msc_cmdline_config.mncc_sock_path) { + rc = mncc_sock_init(msc_network, + msc_cmdline_config.mncc_sock_path); + if (rc) { + fprintf(stderr, "MNCC socket initialization failed. exiting.\n"); + exit(1); + } + } else + 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); + if (rc < 0) + return 2; + + /* BSC stuff is to be split behind an A-interface to be used with + * OsmoBSC, but there is no need to remove it yet. Most of the + * following code until iu_init() is legacy. */ + +#ifdef BUILD_SMPP + smpp_openbsc_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); + if (!msc_network->ctrl) { + printf("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"); + return -1; + } +#endif + + if (msc_ctrl_cmds_install(msc_network) != 0) { + printf("Failed to initialize the MSC control commands.\n"); + return -1; + } + + /* seed the PRNG */ + srand(time(NULL)); + /* 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; + } + + osmo_fsm_log_addr(true); + if (msc_vlr_start(msc_network)) { + fprintf(stderr, "Failed to start VLR\n"); + exit(1); + } + + msc_subscr_conn_init(); + + if (db_prepare()) { + printf("DB: Failed to prepare database.\n"); + return 5; + } + + /* setup the timer */ + osmo_timer_setup(&db_sync_timer, db_sync_timer_cb, NULL); + if (msc_cmdline_config.use_db_counter) + osmo_timer_schedule(&db_sync_timer, DB_SYNC_INTERVAL); + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + /* start the SMS queue */ + if (sms_queue_start(msc_network, 20) != 0) + return -1; + + msc_network->mgcpgw.client = mgcpgw_client_init( + msc_network, &msc_network->mgcpgw.conf); + + if (mgcpgw_client_connect(msc_network->mgcpgw.client)) { + printf("MGCPGW connect failed\n"); + return 7; + } + + /* Set up A-Interface */ + /* TODO: implement A-Interface and remove above legacy stuff. */ + +#ifdef BUILD_IU + /* Set up IuCS */ + iu_init(tall_msc_ctx, "127.0.0.1", 14001, rcvmsg_iu_cs, rx_iu_event); +#endif + + if (msc_cmdline_config.daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + return 6; + } + } + + while (1) { + log_reset_context(); + osmo_select_main(0); + } +} diff --git a/src/osmo-nitb/Makefile.am b/src/osmo-nitb/Makefile.am deleted file mode 100644 index a99334d33..000000000 --- a/src/osmo-nitb/Makefile.am +++ /dev/null @@ -1,46 +0,0 @@ -AM_CPPFLAGS = \ - $(all_includes) \ - -I$(top_srcdir)/include \ - -I$(top_builddir) \ - $(NULL) - -AM_CFLAGS = \ - -Wall \ - $(COVERAGE_CFLAGS) \ - $(LIBOSMOCORE_CFLAGS) \ - $(LIBOSMOGSM_CFLAGS) \ - $(LIBOSMOVTY_CFLAGS) \ - $(LIBOSMOCTRL_CFLAGS) \ - $(LIBOSMOABIS_CFLAGS) \ - $(LIBSMPP34_CFLAGS) \ - $(LIBCRYPTO_CFLAGS) \ - $(NULL) - -AM_LDFLAGS = \ - $(COVERAGE_LDFLAGS) \ - $(NULL) - -bin_PROGRAMS = \ - osmo-nitb \ - $(NULL) - -osmo_nitb_SOURCES = \ - bsc_hack.c \ - $(NULL) - -osmo_nitb_LDADD = \ - $(top_builddir)/src/libbsc/libbsc.a \ - $(top_builddir)/src/libcommon-cs/libcommon-cs.a \ - $(top_builddir)/src/libmsc/libmsc.a \ - $(top_builddir)/src/libvlr/libvlr.a \ - $(top_builddir)/src/libtrau/libtrau.a \ - $(top_builddir)/src/libcommon/libcommon.a \ - $(LIBOSMOGSM_LIBS) \ - $(LIBOSMOVTY_LIBS) \ - $(LIBOSMOCORE_LIBS) \ - $(LIBOSMOCTRL_LIBS) \ - $(LIBOSMOABIS_LIBS) \ - $(LIBSMPP34_LIBS) \ - $(LIBCRYPTO_LIBS) \ - -ldbi \ - $(NULL) diff --git a/src/osmo-nitb/bsc_hack.c b/src/osmo-nitb/bsc_hack.c deleted file mode 100644 index 21585e598..000000000 --- a/src/osmo-nitb/bsc_hack.c +++ /dev/null @@ -1,412 +0,0 @@ -/* A hackish minimal BSC (+MSC +HLR) implementation */ - -/* (C) 2008-2010 by Harald Welte - * (C) 2009-2012 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 . - * - */ - -#include -#include -#include -#include -#include -#include - -#define _GNU_SOURCE -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../bscconfig.h" - -/* MCC and MNC for the Location Area Identifier */ -struct gsm_network *bsc_gsmnet = 0; -static const char *database_name = "sms.db"; -static const char *config_file = "openbsc.cfg"; -static const char *rf_ctrl_path = NULL; -extern const char *openbsc_copyright; -static int daemonize = 0; -static const char *mncc_sock_path = NULL; -static int use_db_counter = 1; - -/* timer to store statistics */ -#define DB_SYNC_INTERVAL 60, 0 -#define EXPIRE_INTERVAL 10, 0 - -static struct osmo_timer_list db_sync_timer; - -static void create_pcap_file(char *file) -{ - mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; - int fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, mode); - - if (fd < 0) { - perror("Failed to open file for pcap"); - return; - } - - e1_set_pcap_fd(fd); -} - -static void print_usage() -{ - printf("Usage: osmo-nitb\n"); -} - -static void print_help() -{ - printf(" Some useful help...\n"); - printf(" -h --help This text.\n"); - printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM 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(" -a --authorize-everyone Authorize every new subscriber. Dangerous!\n"); - printf(" -T --timestamp Prefix every log line with a timestamp.\n"); - printf(" -V --version Print the version of OpenBSC.\n"); - printf(" -P --rtp-proxy Enable the RTP Proxy code inside OpenBSC.\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(" -m --mncc-sock Same as `-M /tmp/bsc_mncc' (deprecated).\n"); - printf(" -C --no-dbcounter Disable regular syncing of counters to database.\n"); - printf(" -r --rf-ctl PATH A unix domain socket to listen for cmds.\n"); - printf(" -p --pcap PATH Write abis communication to pcap trace file.\n"); -} - -static void handle_options(int argc, char **argv) -{ - while (1) { - int option_index = 0, c; - 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'}, - {"authorize-everyone", 0, 0, 'a'}, - {"pcap", 1, 0, 'p'}, - {"timestamp", 0, 0, 'T'}, - {"version", 0, 0, 'V' }, - {"rtp-proxy", 0, 0, 'P'}, - {"log-level", 1, 0, 'e'}, - {"mncc-sock", 0, 0, 'm'}, - {"mncc-sock-path", 1, 0, 'M'}, - {"no-dbcounter", 0, 0, 'C'}, - {"rf-ctl", 1, 0, 'r'}, - {0, 0, 0, 0} - }; - - c = getopt_long(argc, argv, "hd:Dsl:ar:p:TPVc:e:mCr:M:", - long_options, &option_index); - if (c == -1) - break; - - switch (c) { - case 'h': - print_usage(); - print_help(); - exit(0); - case 's': - log_set_use_color(osmo_stderr_target, 0); - break; - case 'd': - log_parse_category_mask(osmo_stderr_target, optarg); - break; - case 'D': - daemonize = 1; - break; - case 'l': - database_name = optarg; - break; - case 'c': - config_file = optarg; - break; - case 'p': - create_pcap_file(optarg); - break; - case 'T': - log_set_print_timestamp(osmo_stderr_target, 1); - break; - case 'P': - ipacc_rtp_direct = 0; - break; - case 'e': - log_set_log_level(osmo_stderr_target, atoi(optarg)); - break; - case 'M': - mncc_sock_path = optarg; - break; - case 'm': - mncc_sock_path = "/tmp/bsc_mncc"; - break; - case 'C': - use_db_counter = 0; - break; - case 'V': - print_version(1); - exit(0); - break; - case 'r': - rf_ctrl_path = optarg; - break; - default: - /* catch unknown options *as well as* missing arguments. */ - fprintf(stderr, "Error in command line options. Exiting.\n"); - exit(-1); - break; - } - } -} - -extern void *tall_vty_ctx; -static void signal_handler(int signal) -{ - fprintf(stdout, "signal %u received\n", signal); - - switch (signal) { - case SIGINT: - bsc_shutdown_net(bsc_gsmnet); - osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL); - sleep(3); - exit(0); - 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 */ - case SIGUSR1: - talloc_report(tall_vty_ctx, stderr); - talloc_report_full(tall_bsc_ctx, stderr); - break; - case SIGUSR2: - talloc_report_full(tall_vty_ctx, stderr); - break; - default: - break; - } -} - -/* timer handling */ -static int _db_store_counter(struct osmo_counter *counter, void *data) -{ - return db_store_counter(counter); -} - -static void db_sync_timer_cb(void *data) -{ - /* store counters to database and re-schedule */ - osmo_counters_for_each(_db_store_counter, NULL); - osmo_timer_schedule(&db_sync_timer, DB_SYNC_INTERVAL); -} - -static void subscr_expire_cb(void *data) -{ - /* TODO expire vlr_subscrs? */ - osmo_timer_schedule(&bsc_gsmnet->subscr_expire_timer, EXPIRE_INTERVAL); -} - -extern int bsc_vty_go_parent(struct vty *vty); - -static struct vty_app_info vty_info = { - .name = "OpenBSC", - .version = PACKAGE_VERSION, - .go_parent_cb = bsc_vty_go_parent, - .is_config_node = bsc_vty_is_config_node, -}; - -int main(int argc, char **argv) -{ - int rc; - - vty_info.copyright = openbsc_copyright; - - tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc"); - talloc_ctx_init(tall_bsc_ctx); - on_dso_load_rrlp(); - on_dso_load_ho_dec(); - - libosmo_abis_init(tall_bsc_ctx); - osmo_init_logging(&log_info); - osmo_stats_init(tall_bsc_ctx); - bts_init(); - vty_init(&vty_info); - - /* Parse options */ - handle_options(argc, argv); - - /* Allocate global gsm_network struct; choose socket/internal MNCC */ - rc = bsc_network_alloc(mncc_sock_path? - mncc_sock_from_cc : int_mncc_recv); - if (rc) { - fprintf(stderr, "Allocation failed. Exiting.\n"); - exit(1); - } - - /* Initialize VTY */ - bsc_vty_init(bsc_gsmnet); - ctrl_vty_init(tall_bsc_ctx); - if (msc_vlr_alloc(bsc_gsmnet)) { - fprintf(stderr, "Failed to allocate VLR\n"); - exit(1); - } - -#ifdef BUILD_SMPP - if (smpp_openbsc_alloc_init(tall_bsc_ctx) < 0) - return -1; -#endif - - /* Initialize MNCC socket if appropriate */ - if (mncc_sock_path) { - rc = mncc_sock_init(bsc_gsmnet, mncc_sock_path); - if (rc) { - fprintf(stderr, "MNCC socket initialization failed. exiting.\n"); - exit(1); - } - } else - DEBUGP(DMNCC, "Using internal MNCC handler.\n"); - - /* - * For osmo-nitb, skip TCH/F for now, because otherwise dyn TS - * always imply the possibility to have a mix of TCH/F and - * TCH/H channels; if two phones request a TCH/F and a TCH/H, - * respectively, they cannot call each other. If we deny TCH/F, - * they will both fall back to TCH/H, and dynamic channels are - * usable. See OS#1778. - * - * A third-party MSC may well be able to handle a TCH/H TCH/F - * mismatch. Moreover, this option may be overwritten in the - * config file or in VTY. - */ - bsc_gsmnet->dyn_ts_allow_tch_f = false; - - /* Read the config */ - rc = bsc_network_configure(config_file); - if (rc < 0) { - fprintf(stderr, "Reading config failed. Exiting.\n"); - exit(1); - } - -#ifdef BUILD_SMPP - smpp_openbsc_start(bsc_gsmnet); -#endif - bsc_api_init(bsc_gsmnet, msc_bsc_api()); - - /* start control interface after reading config for - * ctrl_vty_get_bind_addr() */ - bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet, - ctrl_vty_get_bind_addr(), - OSMO_CTRL_PORT_NITB_BSC); - if (!bsc_gsmnet->ctrl) { - printf("Failed to initialize control interface. Exiting.\n"); - return -1; - } - - if (bsc_base_ctrl_cmds_install() != 0) { - printf("Failed to initialize the BSC control commands.\n"); - return -1; - } - - if (msc_ctrl_cmds_install(bsc_gsmnet) != 0) { - printf("Failed to initialize the MSC control commands.\n"); - return -1; - } - - /* seed the PRNG */ - srand(time(NULL)); - - bsc_gsmnet->bsc_data->rf_ctrl = osmo_bsc_rf_create(rf_ctrl_path, bsc_gsmnet); - if (!bsc_gsmnet->bsc_data->rf_ctrl) { - fprintf(stderr, "Failed to create the RF service.\n"); - exit(1); - } - - osmo_fsm_log_addr(true); - if (msc_vlr_start(bsc_gsmnet)) { - fprintf(stderr, "Failed to start VLR\n"); - exit(1); - } - - msc_subscr_conn_init(); - - if (db_init(database_name)) { - printf("DB: Failed to init database. Please check the option settings.\n"); - return -1; - } - printf("DB: Database initialized.\n"); - - if (db_prepare()) { - printf("DB: Failed to prepare database.\n"); - return -1; - } - printf("DB: Database prepared.\n"); - - /* setup the timer */ - osmo_timer_setup(&db_sync_timer, db_sync_timer_cb, NULL); - if (use_db_counter) - osmo_timer_schedule(&db_sync_timer, DB_SYNC_INTERVAL); - - osmo_timer_setup(&bsc_gsmnet->subscr_expire_timer, subscr_expire_cb, - NULL); - osmo_timer_schedule(&bsc_gsmnet->subscr_expire_timer, EXPIRE_INTERVAL); - - signal(SIGINT, &signal_handler); - signal(SIGABRT, &signal_handler); - signal(SIGUSR1, &signal_handler); - signal(SIGUSR2, &signal_handler); - osmo_init_ignore_signals(); - - /* start the SMS queue */ - if (sms_queue_start(bsc_gsmnet, 20) != 0) - return -1; - - if (daemonize) { - rc = osmo_daemonize(); - if (rc < 0) { - perror("Error during daemonize"); - exit(1); - } - } - - while (1) { - log_reset_context(); - osmo_select_main(0); - } -} -- cgit v1.2.3