diff options
Diffstat (limited to 'src/osmo-bsc')
-rw-r--r-- | src/osmo-bsc/Makefile.am | 55 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_api.c | 550 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_audio.c | 73 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_bssap.c | 548 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_ctrl.c | 680 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_filter.c | 381 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_grace.c | 169 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_main.c | 301 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_msc.c | 586 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_sccp.c | 328 | ||||
-rw-r--r-- | src/osmo-bsc/osmo_bsc_vty.c | 945 |
11 files changed, 4616 insertions, 0 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am new file mode 100644 index 000000000..ae9410c9d --- /dev/null +++ b/src/osmo-bsc/Makefile.am @@ -0,0 +1,55 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir) \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBOSMOSCCP_CFLAGS) \ + $(COVERAGE_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(COVERAGE_LDFLAGS) \ + $(NULL) + +bin_PROGRAMS = \ + osmo-bsc \ + $(NULL) + +osmo_bsc_SOURCES = \ + osmo_bsc_main.c \ + osmo_bsc_vty.c \ + osmo_bsc_api.c \ + osmo_bsc_grace.c \ + osmo_bsc_msc.c \ + osmo_bsc_sccp.c \ + osmo_bsc_filter.c \ + osmo_bsc_bssap.c \ + osmo_bsc_audio.c \ + osmo_bsc_ctrl.c \ + $(NULL) + +# once again since TRAU uses CC symbol :( +osmo_bsc_LDADD = \ + $(top_builddir)/src/libfilter/libfilter.a \ + $(top_builddir)/src/libbsc/libbsc.a \ + $(top_builddir)/src/libcommon-cs/libcommon-cs.a \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libtrau/libtrau.a \ + $(top_builddir)/src/libcommon/libcommon.a \ + $(LIBOSMOSCCP_LIBS) \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(COVERAGE_LDFLAGS) \ + $(LIBOSMOABIS_LIBS) \ + $(NULL) diff --git a/src/osmo-bsc/osmo_bsc_api.c b/src/osmo-bsc/osmo_bsc_api.c new file mode 100644 index 000000000..bac5e4717 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_api.c @@ -0,0 +1,550 @@ +/* (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/osmo_bsc.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/debug.h> + +#include <openbsc/gsm_04_80.h> + +#include <osmocom/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/gsm0808.h> + +#include <osmocom/sccp/sccp.h> + +#define return_when_not_connected(conn) \ + if (!conn->sccp_con) {\ + LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \ + return; \ + } + +#define return_when_not_connected_val(conn, ret) \ + if (!conn->sccp_con) {\ + LOGP(DMSC, LOGL_ERROR, "MSC Connection not present.\n"); \ + return ret; \ + } + +#define queue_msg_or_return(resp) \ + if (!resp) { \ + LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); \ + return; \ + } \ + bsc_queue_for_msc(conn->sccp_con, resp); + +static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause); +static int complete_layer3(struct gsm_subscriber_connection *conn, + struct msgb *msg, struct bsc_msc_data *msc); + +static uint16_t get_network_code_for_msc(struct bsc_msc_data *msc) +{ + if (msc->core_mnc != -1) + return msc->core_mnc; + return msc->network->network_code; +} + +static uint16_t get_country_code_for_msc(struct bsc_msc_data *msc) +{ + if (msc->core_mcc != -1) + return msc->core_mcc; + return msc->network->country_code; +} + +static uint16_t get_lac_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts) +{ + if (msc->core_lac != -1) + return msc->core_lac; + return bts->location_area_code; +} + +static uint16_t get_ci_for_msc(struct bsc_msc_data *msc, struct gsm_bts *bts) +{ + if (msc->core_ci != -1) + return msc->core_ci; + return bts->cell_identity; +} + +static void bsc_maybe_lu_reject(struct gsm_subscriber_connection *conn, int con_type, int cause) +{ + struct msgb *msg; + + /* ignore cm service request or such */ + if (con_type != FLT_CON_TYPE_LU) + return; + + msg = gsm48_create_loc_upd_rej(cause); + if (!msg) { + LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n"); + return; + } + + msg->lchan = conn->lchan; + gsm0808_submit_dtap(conn, msg, 0, 0); +} + +static int bsc_filter_initial(struct osmo_bsc_data *bsc, + struct bsc_msc_data *msc, + struct gsm_subscriber_connection *conn, + struct msgb *msg, char **imsi, int *con_type, + int *lu_cause) +{ + struct bsc_filter_request req; + struct bsc_filter_reject_cause cause; + struct gsm48_hdr *gh = msgb_l3(msg); + int rc; + + req.ctx = conn; + req.black_list = NULL; + req.access_lists = bsc_access_lists(); + req.local_lst_name = msc->acc_lst_name; + req.global_lst_name = conn->bts->network->bsc_data->acc_lst_name; + req.bsc_nr = 0; + + rc = bsc_msg_filter_initial(gh, msgb_l3len(msg), &req, + con_type, imsi, &cause); + *lu_cause = cause.lu_reject_cause; + return rc; +} + +static int bsc_filter_data(struct gsm_subscriber_connection *conn, + struct msgb *msg, int *lu_cause) +{ + struct bsc_filter_request req; + struct gsm48_hdr *gh = msgb_l3(msg); + struct bsc_filter_reject_cause cause; + int rc; + + req.ctx = conn; + req.black_list = NULL; + req.access_lists = bsc_access_lists(); + req.local_lst_name = conn->sccp_con->msc->acc_lst_name; + req.global_lst_name = conn->bts->network->bsc_data->acc_lst_name; + req.bsc_nr = 0; + + rc = bsc_msg_filter_data(gh, msgb_l3len(msg), &req, + &conn->sccp_con->filter_state, + &cause); + *lu_cause = cause.lu_reject_cause; + return rc; +} + +static void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci) +{ + struct msgb *resp; + return_when_not_connected(conn); + + LOGP(DMSC, LOGL_NOTICE, "Tx MSC SAPI N REJECT DLCI=0x%02x\n", dlci); + + resp = gsm0808_create_sapi_reject(dlci); + queue_msg_or_return(resp); +} + +static void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg, uint8_t chosen_encr) +{ + struct msgb *resp; + return_when_not_connected(conn); + + LOGP(DMSC, LOGL_DEBUG, "CIPHER MODE COMPLETE from MS, forwarding to MSC\n"); + resp = gsm0808_create_cipher_complete(msg, chosen_encr); + queue_msg_or_return(resp); +} + +static void bsc_send_ussd_no_srv(struct gsm_subscriber_connection *conn, + struct msgb *msg, const char *text) +{ + struct gsm48_hdr *gh; + int8_t pdisc; + uint8_t mtype; + int drop_message = 1; + + if (!text) + return; + + if (!msg || msgb_l3len(msg) < sizeof(*gh)) + return; + + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + mtype = gsm48_hdr_msg_type(gh); + + /* Is CM service request? */ + if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) { + struct gsm48_service_request *cm; + + cm = (struct gsm48_service_request *) &gh->data[0]; + + /* Is type SMS or call? */ + if (cm->cm_service_type == GSM48_CMSERV_SMS) + drop_message = 0; + else if (cm->cm_service_type == GSM48_CMSERV_MO_CALL_PACKET) + drop_message = 0; + } + + if (drop_message) { + LOGP(DMSC, LOGL_DEBUG, "Skipping (not sending) USSD message: '%s'\n", text); + return; + } + + LOGP(DMSC, LOGL_INFO, "Sending CM Service Accept\n"); + gsm48_tx_mm_serv_ack(conn); + + LOGP(DMSC, LOGL_INFO, "Sending USSD message: '%s'\n", text); + bsc_send_ussd_notify(conn, 1, text); + bsc_send_ussd_release_complete(conn); +} + +/* + * Instruct to reserve data for a new connectiom, create the complete + * layer three message, send it to open the connection. + */ +static int bsc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg, + uint16_t chosen_channel) +{ + struct bsc_msc_data *msc; + + LOGP(DMSC, LOGL_INFO, "Tx MSC COMPL L3\n"); + + /* find the MSC link we want to use */ + msc = bsc_find_msc(conn, msg); + if (!msc) { + LOGP(DMSC, LOGL_ERROR, "Failed to find a MSC for a connection.\n"); + bsc_send_ussd_no_srv(conn, msg, + conn->bts->network->bsc_data->ussd_no_msc_txt); + return -1; + } + + return complete_layer3(conn, msg, msc); +} + +static int complete_layer3(struct gsm_subscriber_connection *conn, + struct msgb *msg, struct bsc_msc_data *msc) +{ + int con_type, rc, lu_cause; + char *imsi = NULL; + struct timeval tv; + struct msgb *resp; + uint16_t network_code; + uint16_t country_code; + uint16_t lac; + uint16_t ci; + enum bsc_con ret; + int send_ping = msc->advanced_ping; + + /* Advanced ping/pong handling */ + if (osmo_timer_pending(&msc->pong_timer)) + send_ping = 0; + if (msc->ping_timeout <= 0) + send_ping = 0; + if (send_ping && osmo_timer_remaining(&msc->ping_timer, NULL, &tv) == -1) + send_ping = 0; + + /* Check the filter */ + rc = bsc_filter_initial(msc->network->bsc_data, msc, conn, msg, + &imsi, &con_type, &lu_cause); + if (rc < 0) { + bsc_maybe_lu_reject(conn, con_type, lu_cause); + return BSC_API_CONN_POL_REJECT; + } + + /* allocate resource for a new connection */ + ret = bsc_create_new_connection(conn, msc, send_ping); + + if (ret != BSC_CON_SUCCESS) { + /* allocation has failed */ + if (ret == BSC_CON_REJECT_NO_LINK) + bsc_send_ussd_no_srv(conn, msg, msc->ussd_msc_lost_txt); + else if (ret == BSC_CON_REJECT_RF_GRACE) + bsc_send_ussd_no_srv(conn, msg, msc->ussd_grace_txt); + + return BSC_API_CONN_POL_REJECT; + } + + if (imsi) + conn->sccp_con->filter_state.imsi = talloc_steal(conn, imsi); + conn->sccp_con->filter_state.con_type = con_type; + + /* check return value, if failed check msg for and send USSD */ + + network_code = get_network_code_for_msc(conn->sccp_con->msc); + country_code = get_country_code_for_msc(conn->sccp_con->msc); + lac = get_lac_for_msc(conn->sccp_con->msc, conn->bts); + ci = get_ci_for_msc(conn->sccp_con->msc, conn->bts); + + bsc_scan_bts_msg(conn, msg); + + resp = gsm0808_create_layer3(msg, network_code, country_code, lac, ci); + if (!resp) { + LOGP(DMSC, LOGL_DEBUG, "Failed to create layer3 message.\n"); + sccp_connection_free(conn->sccp_con->sccp); + bsc_delete_connection(conn->sccp_con); + return BSC_API_CONN_POL_REJECT; + } + + if (bsc_open_connection(conn->sccp_con, resp) != 0) { + sccp_connection_free(conn->sccp_con->sccp); + bsc_delete_connection(conn->sccp_con); + msgb_free(resp); + return BSC_API_CONN_POL_REJECT; + } + + return BSC_API_CONN_POL_ACCEPT; +} + +/* + * Plastic surgery... we want to give up the current connection + */ +static int move_to_msc(struct gsm_subscriber_connection *_conn, + struct msgb *msg, struct bsc_msc_data *msc) +{ + struct osmo_bsc_sccp_con *old_con = _conn->sccp_con; + + /* + * 1. Give up the old connection. + * This happens by sending a clear request to the MSC, + * it should end with the MSC releasing the connection. + */ + old_con->conn = NULL; + bsc_clear_request(_conn, 0); + + /* + * 2. Attempt to create a new connection to the local + * MSC. If it fails the caller will need to handle this + * properly. + */ + _conn->sccp_con = NULL; + if (complete_layer3(_conn, msg, msc) != BSC_API_CONN_POL_ACCEPT) { + gsm0808_clear(_conn); + bsc_subscr_con_free(_conn); + return 1; + } + + return 2; +} + +static int handle_cc_setup(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + uint8_t mtype = gsm48_hdr_msg_type(gh); + + struct bsc_msc_data *msc; + struct gsm_mncc_number called; + struct tlv_parsed tp; + unsigned payload_len; + + char _dest_nr[35]; + + /* + * Do we have a setup message here? if not return fast. + */ + if (pdisc != GSM48_PDISC_CC || mtype != GSM48_MT_CC_SETUP) + return 0; + + payload_len = msgb_l3len(msg) - sizeof(*gh); + + tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0); + if (!TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) { + LOGP(DMSC, LOGL_ERROR, "Called BCD not present in setup.\n"); + return -1; + } + + memset(&called, 0, sizeof(called)); + gsm48_decode_called(&called, + TLVP_VAL(&tp, GSM48_IE_CALLED_BCD) - 1); + + if (called.plan != 1 && called.plan != 0) + return 0; + + if (called.plan == 1 && called.type == 1) { + _dest_nr[0] = _dest_nr[1] = '0'; + memcpy(_dest_nr + 2, called.number, sizeof(called.number)); + } else + memcpy(_dest_nr, called.number, sizeof(called.number)); + + /* + * Check if the connection should be moved... + */ + llist_for_each_entry(msc, &conn->bts->network->bsc_data->mscs, entry) { + if (msc->type != MSC_CON_TYPE_LOCAL) + continue; + if (!msc->local_pref) + continue; + if (regexec(&msc->local_pref_reg, _dest_nr, 0, NULL, 0) != 0) + continue; + + return move_to_msc(conn, msg, msc); + } + + return 0; +} + + +static void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) +{ + int lu_cause; + struct msgb *resp; + return_when_not_connected(conn); + + LOGP(DMSC, LOGL_INFO, "Tx MSC DTAP LINK_ID=0x%02x\n", link_id); + + /* + * We might want to move this connection to a new MSC. Ask someone + * to handle it. If it was handled we will return. + */ + if (handle_cc_setup(conn, msg) >= 1) + return; + + /* Check the filter */ + if (bsc_filter_data(conn, msg, &lu_cause) < 0) { + bsc_maybe_lu_reject(conn, + conn->sccp_con->filter_state.con_type, + lu_cause); + bsc_clear_request(conn, 0); + return; + } + + bsc_scan_bts_msg(conn, msg); + + resp = gsm0808_create_dtap(msg, link_id); + queue_msg_or_return(resp); +} + +static void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause, + uint8_t chosen_channel, uint8_t encr_alg_id, + uint8_t speech_model) +{ + struct msgb *resp; + return_when_not_connected(conn); + + LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN COMPL\n"); + + resp = gsm0808_create_assignment_completed(rr_cause, chosen_channel, + encr_alg_id, speech_model); + queue_msg_or_return(resp); +} + +static void bsc_assign_fail(struct gsm_subscriber_connection *conn, + uint8_t cause, uint8_t *rr_cause) +{ + struct msgb *resp; + return_when_not_connected(conn); + + LOGP(DMSC, LOGL_INFO, "Tx MSC ASSIGN FAIL\n"); + + resp = gsm0808_create_assignment_failure(cause, rr_cause); + queue_msg_or_return(resp); +} + +static int bsc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause) +{ + struct osmo_bsc_sccp_con *sccp; + struct msgb *resp; + return_when_not_connected_val(conn, 1); + + LOGP(DMSC, LOGL_INFO, "Tx MSC CLEAR REQUEST\n"); + + /* + * Remove the connection from BSC<->SCCP part, the SCCP part + * will either be cleared by channel release or MSC disconnect + */ + sccp = conn->sccp_con; + sccp->conn = NULL; + conn->sccp_con = NULL; + + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate response.\n"); + return 1; + } + + bsc_queue_for_msc(sccp, resp); + return 1; +} + +static void bsc_cm_update(struct gsm_subscriber_connection *conn, + const uint8_t *cm2, uint8_t cm2_len, + const uint8_t *cm3, uint8_t cm3_len) +{ + struct msgb *resp; + return_when_not_connected(conn); + + resp = gsm0808_create_classmark_update(cm2, cm2_len, cm3, cm3_len); + + queue_msg_or_return(resp); +} + +static void bsc_mr_config(struct gsm_subscriber_connection *conn, + struct gsm_lchan *lchan, int full_rate) +{ + struct bsc_msc_data *msc; + struct gsm48_multi_rate_conf *ms_conf, *bts_conf; + + if (!conn->sccp_con) { + LOGP(DMSC, LOGL_ERROR, + "No msc data available on conn %p. Audio will be broken.\n", + conn); + return; + } + + msc = conn->sccp_con->msc; + + /* initialize the data structure */ + lchan->mr_ms_lv[0] = sizeof(*ms_conf); + lchan->mr_bts_lv[0] = sizeof(*bts_conf); + ms_conf = (struct gsm48_multi_rate_conf *) &lchan->mr_ms_lv[1]; + bts_conf = (struct gsm48_multi_rate_conf *) &lchan->mr_bts_lv[1]; + memset(ms_conf, 0, sizeof(*ms_conf)); + memset(bts_conf, 0, sizeof(*bts_conf)); + + bts_conf->ver = ms_conf->ver = 1; + bts_conf->icmi = ms_conf->icmi = 1; + + /* maybe gcc see's it is copy of _one_ byte */ + bts_conf->m4_75 = ms_conf->m4_75 = msc->amr_conf.m4_75; + bts_conf->m5_15 = ms_conf->m5_15 = msc->amr_conf.m5_15; + bts_conf->m5_90 = ms_conf->m5_90 = msc->amr_conf.m5_90; + bts_conf->m6_70 = ms_conf->m6_70 = msc->amr_conf.m6_70; + bts_conf->m7_40 = ms_conf->m7_40 = msc->amr_conf.m7_40; + bts_conf->m7_95 = ms_conf->m7_95 = msc->amr_conf.m7_95; + if (full_rate) { + bts_conf->m10_2 = ms_conf->m10_2 = msc->amr_conf.m10_2; + bts_conf->m12_2 = ms_conf->m12_2 = msc->amr_conf.m12_2; + } + + /* now copy this into the bts structure */ + memcpy(lchan->mr_bts_lv, lchan->mr_ms_lv, sizeof(lchan->mr_ms_lv)); +} + +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, + .assign_compl = bsc_assign_compl, + .assign_fail = bsc_assign_fail, + .clear_request = bsc_clear_request, + .classmark_chg = bsc_cm_update, + .mr_config = bsc_mr_config, +}; + +struct bsc_api *osmo_bsc_api() +{ + return &bsc_handler; +} diff --git a/src/osmo-bsc/osmo_bsc_audio.c b/src/osmo-bsc/osmo_bsc_audio.c new file mode 100644 index 000000000..116020900 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_audio.c @@ -0,0 +1,73 @@ +/* + * ipaccess audio handling + * + * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2010 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/bsc_msc_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/abis_rsl.h> +#include <openbsc/gsm_data.h> +#include <openbsc/debug.h> +#include <openbsc/signal.h> + +#include <arpa/inet.h> + +static int handle_abisip_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_subscriber_connection *con; + struct gsm_lchan *lchan = signal_data; + int rc; + + if (subsys != SS_ABISIP) + return 0; + + con = lchan->conn; + if (!con || !con->sccp_con) + return 0; + + switch (signal) { + case S_ABISIP_CRCX_ACK: + /* + * TODO: handle handover here... then the audio should go to + * the old mgcp port.. + */ + /* we can ask it to connect now */ + LOGP(DMSC, LOGL_DEBUG, "Connecting BTS to port: %d conn: %d\n", + con->sccp_con->rtp_port, lchan->abis_ip.conn_id); + + rc = rsl_ipacc_mdcx(lchan, ntohl(INADDR_ANY), + con->sccp_con->rtp_port, + lchan->abis_ip.rtp_payload2); + if (rc < 0) { + LOGP(DMSC, LOGL_ERROR, "Failed to send MDCX: %d\n", rc); + return rc; + } + break; + } + + return 0; +} + +int osmo_bsc_audio_init(struct gsm_network *net) +{ + osmo_signal_register_handler(SS_ABISIP, handle_abisip_signal, net); + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c new file mode 100644 index 000000000..100f66441 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_bssap.c @@ -0,0 +1,548 @@ +/* GSM 08.08 BSSMAP handling */ +/* (C) 2009-2012 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2012 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/osmo_bsc.h> +#include <openbsc/osmo_bsc_grace.h> +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/debug.h> +#include <openbsc/bsc_subscriber.h> +#include <openbsc/mgcp.h> +#include <openbsc/paging.h> + +#include <osmocom/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/gsm0808.h> + +/* + * helpers for the assignment command + */ +enum gsm0808_permitted_speech audio_support_to_gsm88(struct gsm_audio_support *audio) +{ + if (audio->hr) { + switch (audio->ver) { + case 1: + return GSM0808_PERM_HR1; + break; + case 2: + return GSM0808_PERM_HR2; + break; + case 3: + return GSM0808_PERM_HR3; + break; + default: + LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver); + return GSM0808_PERM_FR1; + } + } else { + switch (audio->ver) { + case 1: + return GSM0808_PERM_FR1; + break; + case 2: + return GSM0808_PERM_FR2; + break; + case 3: + return GSM0808_PERM_FR3; + break; + default: + LOGP(DMSC, LOGL_ERROR, "Wrong speech mode: %d\n", audio->ver); + return GSM0808_PERM_HR1; + } + } +} + +enum gsm48_chan_mode gsm88_to_chan_mode(enum gsm0808_permitted_speech speech) +{ + switch (speech) { + case GSM0808_PERM_HR1: + case GSM0808_PERM_FR1: + return GSM48_CMODE_SPEECH_V1; + break; + case GSM0808_PERM_HR2: + case GSM0808_PERM_FR2: + return GSM48_CMODE_SPEECH_EFR; + break; + case GSM0808_PERM_HR3: + case GSM0808_PERM_FR3: + return GSM48_CMODE_SPEECH_AMR; + break; + } + + LOGP(DMSC, LOGL_FATAL, "Should not be reached.\n"); + return GSM48_CMODE_SPEECH_AMR; +} + +static int bssmap_handle_reset_ack(struct bsc_msc_data *msc, + struct msgb *msg, unsigned int length) +{ + LOGP(DMSC, LOGL_NOTICE, "Reset ACK from MSC\n"); + return 0; +} + +/* GSM 08.08 § 3.2.1.19 */ +static int bssmap_handle_paging(struct bsc_msc_data *msc, + struct msgb *msg, unsigned int payload_length) +{ + struct bsc_subscr *subscr; + struct tlv_parsed tp; + char mi_string[GSM48_MI_SIZE]; + uint32_t tmsi = GSM_RESERVED_TMSI; + unsigned int lac = GSM_LAC_RESERVED_ALL_BTS; + uint8_t data_length; + const uint8_t *data; + uint8_t chan_needed = RSL_CHANNEED_ANY; + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0); + + if (!TLVP_PRESENT(&tp, GSM0808_IE_IMSI)) { + LOGP(DMSC, LOGL_ERROR, "Mandatory IMSI not present.\n"); + return -1; + } else if ((TLVP_VAL(&tp, GSM0808_IE_IMSI)[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI) { + LOGP(DMSC, LOGL_ERROR, "Wrong content in the IMSI\n"); + return -1; + } + + if (!TLVP_PRESENT(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST)) { + LOGP(DMSC, LOGL_ERROR, "Mandatory CELL IDENTIFIER LIST not present.\n"); + return -1; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_TMSI) && + TLVP_LEN(&tp, GSM0808_IE_TMSI) == 4) { + tmsi = ntohl(tlvp_val32_unal(&tp, GSM0808_IE_TMSI)); + } + + /* + * parse the IMSI + */ + gsm48_mi_to_string(mi_string, sizeof(mi_string), + TLVP_VAL(&tp, GSM0808_IE_IMSI), TLVP_LEN(&tp, GSM0808_IE_IMSI)); + + /* + * parse the cell identifier list + */ + data_length = TLVP_LEN(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + data = TLVP_VAL(&tp, GSM0808_IE_CELL_IDENTIFIER_LIST); + + /* + * Support paging to all network or one BTS at one LAC + */ + if (data_length == 3 && data[0] == CELL_IDENT_LAC) { + lac = osmo_load16be(&data[1]); + } else if (data_length > 1 || (data[0] & 0x0f) != CELL_IDENT_BSS) { + LOGP(DMSC, LOGL_ERROR, "Unsupported Cell Identifier List: %s\n", osmo_hexdump(data, data_length)); + return -1; + } + + if (TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_NEEDED) && TLVP_LEN(&tp, GSM0808_IE_CHANNEL_NEEDED) == 1) + chan_needed = TLVP_VAL(&tp, GSM0808_IE_CHANNEL_NEEDED)[0] & 0x03; + + if (TLVP_PRESENT(&tp, GSM0808_IE_EMLPP_PRIORITY)) { + LOGP(DMSC, LOGL_ERROR, "eMLPP is not handled\n"); + } + + subscr = bsc_subscr_find_or_create_by_imsi(msc->network->bsc_subscribers, + mi_string); + if (!subscr) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate a subscriber for %s\n", mi_string); + return -1; + } + + subscr->lac = lac; + subscr->tmsi = tmsi; + + LOGP(DMSC, LOGL_INFO, "Paging request from MSC IMSI: '%s' TMSI: '0x%x/%u' LAC: 0x%x\n", mi_string, tmsi, tmsi, lac); + bsc_grace_paging_request(msc->network->bsc_data->rf_ctrl->policy, + subscr, chan_needed, msc); + return 0; +} + +/* + * GSM 08.08 § 3.1.9.1 and 3.2.1.21... + * release our gsm_subscriber_connection and send message + */ +static int bssmap_handle_clear_command(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int payload_length) +{ + struct msgb *resp; + + /* TODO: handle the cause of this package */ + + if (conn->conn) { + LOGP(DMSC, LOGL_INFO, "Releasing all transactions on %p\n", conn); + gsm0808_clear(conn->conn); + bsc_subscr_con_free(conn->conn); + conn->conn = NULL; + } + + /* send the clear complete message */ + resp = gsm0808_create_clear_complete(); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Sending clear complete failed.\n"); + return -1; + } + + bsc_queue_for_msc(conn, resp); + return 0; +} + +/* + * GSM 08.08 § 3.4.7 cipher mode handling. We will have to pick + * the cipher to be used for this. In case we are already using + * a cipher we will have to send cipher mode reject to the MSC, + * otherwise we will have to pick something that we and the MS + * is supporting. Currently we are doing it in a rather static + * way by picking one ecnryption or no encrytpion. + */ +static int bssmap_handle_cipher_mode(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int payload_length) +{ + uint16_t len; + struct gsm_network *network = NULL; + const uint8_t *data; + struct tlv_parsed tp; + struct msgb *resp; + int reject_cause = -1; + int include_imeisv = 1; + + if (!conn->conn) { + LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); + goto reject; + } + + if (conn->ciphering_handled) { + LOGP(DMSC, LOGL_ERROR, "Already seen ciphering command. Protocol Error.\n"); + goto reject; + } + + conn->ciphering_handled = 1; + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, payload_length - 1, 0, 0); + if (!TLVP_PRESENT(&tp, GSM0808_IE_ENCRYPTION_INFORMATION)) { + LOGP(DMSC, LOGL_ERROR, "IE Encryption Information missing.\n"); + goto reject; + } + + /* + * check if our global setting is allowed + * - Currently we check for A5/0 and A5/1 + * - Copy the key if that is necessary + * - Otherwise reject + */ + len = TLVP_LEN(&tp, GSM0808_IE_ENCRYPTION_INFORMATION); + if (len < 1) { + LOGP(DMSC, LOGL_ERROR, "IE Encryption Information is too short.\n"); + goto reject; + } + + network = conn->conn->bts->network; + data = TLVP_VAL(&tp, GSM0808_IE_ENCRYPTION_INFORMATION); + + if (TLVP_PRESENT(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)) + include_imeisv = TLVP_VAL(&tp, GSM0808_IE_CIPHER_RESPONSE_MODE)[0] & 0x1; + + if (network->a5_encryption == 0 && (data[0] & 0x1) == 0x1) { + gsm0808_cipher_mode(conn->conn, 0, NULL, 0, include_imeisv); + } else if (network->a5_encryption != 0 && (data[0] & 0x2) == 0x2) { + gsm0808_cipher_mode(conn->conn, 1, &data[1], len - 1, include_imeisv); + } else { + LOGP(DMSC, LOGL_ERROR, "Can not select encryption...\n"); + goto reject; + } + + return 0; + +reject: + resp = gsm0808_create_cipher_reject(reject_cause); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Sending the cipher reject failed.\n"); + return -1; + } + + bsc_queue_for_msc(conn, resp); + return -1; +} + +/* + * Handle the assignment request message. + * + * See §3.2.1.1 for the message type + */ +static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int length) +{ + struct msgb *resp; + struct bsc_msc_data *msc; + struct tlv_parsed tp; + uint8_t *data; + uint8_t timeslot; + uint8_t multiplex; + enum gsm48_chan_mode chan_mode = GSM48_CMODE_SIGN; + int i, supported, port, full_rate = -1; + + if (!conn->conn) { + LOGP(DMSC, LOGL_ERROR, "No lchan/msc_data in cipher mode command.\n"); + return -1; + } + + tlv_parse(&tp, gsm0808_att_tlvdef(), msg->l4h + 1, length - 1, 0, 0); + + if (!TLVP_PRESENT(&tp, GSM0808_IE_CHANNEL_TYPE)) { + LOGP(DMSC, LOGL_ERROR, "Mandatory channel type not present.\n"); + goto reject; + } + + if (!TLVP_PRESENT(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)) { + LOGP(DMSC, LOGL_ERROR, "Identity code missing. Audio routing will not work.\n"); + goto reject; + } + + conn->cic = osmo_load16be(TLVP_VAL(&tp, GSM0808_IE_CIRCUIT_IDENTITY_CODE)); + timeslot = conn->cic & 0x1f; + multiplex = (conn->cic & ~0x1f) >> 5; + + /* + * Currently we only support a limited subset of all + * possible channel types. The limitation ends by not using + * multi-slot, limiting the channel coding, speech... + */ + if (TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE) < 3) { + LOGP(DMSC, LOGL_ERROR, "ChannelType len !=3 not supported: %d\n", + TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE)); + goto reject; + } + + /* + * Try to figure out if we support the proposed speech codecs. For + * now we will always pick the full rate codecs. + */ + + data = (uint8_t *) TLVP_VAL(&tp, GSM0808_IE_CHANNEL_TYPE); + if ((data[0] & 0xf) != 0x1) { + LOGP(DMSC, LOGL_ERROR, "ChannelType != speech: %d\n", data[0]); + goto reject; + } + + /* + * go through the list of preferred codecs of our gsm network + * and try to find it among the permitted codecs. If we found + * it we will send chan_mode to the right mode and break the + * inner loop. The outer loop will exit due chan_mode having + * the correct value. + */ + full_rate = 0; + msc = conn->msc; + for (supported = 0; + chan_mode == GSM48_CMODE_SIGN && supported < msc->audio_length; + ++supported) { + + int perm_val = audio_support_to_gsm88(msc->audio_support[supported]); + for (i = 2; i < TLVP_LEN(&tp, GSM0808_IE_CHANNEL_TYPE); ++i) { + if ((data[i] & 0x7f) == perm_val) { + chan_mode = gsm88_to_chan_mode(perm_val); + full_rate = (data[i] & 0x4) == 0; + break; + } else if ((data[i] & 0x80) == 0x00) { + break; + } + } + } + + if (chan_mode == GSM48_CMODE_SIGN) { + LOGP(DMSC, LOGL_ERROR, "No supported audio type found.\n"); + goto reject; + } + + /* map it to a MGCP Endpoint and a RTP port */ + port = mgcp_timeslot_to_endpoint(multiplex, timeslot); + conn->rtp_port = rtp_calculate_port(port, msc->rtp_base); + + return gsm0808_assign_req(conn->conn, chan_mode, full_rate); + +reject: + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, NULL); + if (!resp) { + LOGP(DMSC, LOGL_ERROR, "Channel allocation failure.\n"); + return -1; + } + + bsc_queue_for_msc(conn, resp); + return -1; +} + +static int bssmap_rcvmsg_udt(struct bsc_msc_data *msc, + struct msgb *msg, unsigned int length) +{ + int ret = 0; + + if (length < 1) { + LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length); + return -1; + } + + LOGP(DMSC, LOGL_INFO, "Rx MSC UDT BSSMAP %s\n", + gsm0808_bssmap_name(msg->l4h[0])); + + switch (msg->l4h[0]) { + case BSS_MAP_MSG_RESET_ACKNOWLEDGE: + ret = bssmap_handle_reset_ack(msc, msg, length); + break; + case BSS_MAP_MSG_PAGING: + ret = bssmap_handle_paging(msc, msg, length); + break; + } + + return ret; +} + +static int bssmap_rcvmsg_dt1(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int length) +{ + int ret = 0; + + if (length < 1) { + LOGP(DMSC, LOGL_ERROR, "Not enough room: %d\n", length); + return -1; + } + + LOGP(DMSC, LOGL_INFO, "Rx MSC DT1 BSSMAP %s\n", + gsm0808_bssmap_name(msg->l4h[0])); + + switch (msg->l4h[0]) { + case BSS_MAP_MSG_CLEAR_CMD: + ret = bssmap_handle_clear_command(conn, msg, length); + break; + case BSS_MAP_MSG_CIPHER_MODE_CMD: + ret = bssmap_handle_cipher_mode(conn, msg, length); + break; + case BSS_MAP_MSG_ASSIGMENT_RQST: + ret = bssmap_handle_assignm_req(conn, msg, length); + break; + default: + LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n", + gsm0808_bssmap_name(msg->l4h[0])); + break; + } + + return ret; +} + +static int dtap_rcvmsg(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int length) +{ + struct dtap_header *header; + struct msgb *gsm48; + uint8_t *data; + int rc, dtap_rc; + + LOGP(DMSC, LOGL_DEBUG, "Rx MSC DTAP: %s\n", + osmo_hexdump(msg->l3h, length)); + + if (!conn->conn) { + LOGP(DMSC, LOGL_ERROR, "No subscriber connection available\n"); + return -1; + } + + header = (struct dtap_header *) msg->l3h; + if (sizeof(*header) >= length) { + LOGP(DMSC, LOGL_ERROR, "The DTAP header does not fit. Wanted: %zu got: %u\n", sizeof(*header), length); + LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length)); + return -1; + } + + if (header->length > length - sizeof(*header)) { + LOGP(DMSC, LOGL_ERROR, "The DTAP l4 information does not fit: header: %u length: %u\n", header->length, length); + LOGP(DMSC, LOGL_ERROR, "hex: %s\n", osmo_hexdump(msg->l3h, length)); + return -1; + } + + LOGP(DMSC, LOGL_INFO, "Rx MSC DTAP, SAPI: %u CHAN: %u\n", header->link_id & 0x07, header->link_id & 0xC0); + + /* forward the data */ + gsm48 = gsm48_msgb_alloc_name("GSM 04.08 DTAP RCV"); + if (!gsm48) { + LOGP(DMSC, LOGL_ERROR, "Allocation of the message failed.\n"); + return -1; + } + + gsm48->l3h = gsm48->data; + data = msgb_put(gsm48, length - sizeof(*header)); + memcpy(data, msg->l3h + sizeof(*header), length - sizeof(*header)); + + /* pass it to the filter for extra actions */ + rc = bsc_scan_msc_msg(conn->conn, gsm48); + dtap_rc = gsm0808_submit_dtap(conn->conn, gsm48, header->link_id, 1); + if (rc == BSS_SEND_USSD) + bsc_send_welcome_ussd(conn->conn); + return dtap_rc; +} + +int bsc_handle_udt(struct bsc_msc_data *msc, + struct msgb *msgb, unsigned int length) +{ + struct bssmap_header *bs; + + LOGP(DMSC, LOGL_DEBUG, "Rx MSC UDT: %s\n", + osmo_hexdump(msgb->l3h, length)); + + if (length < sizeof(*bs)) { + LOGP(DMSC, LOGL_ERROR, "The header is too short.\n"); + return -1; + } + + bs = (struct bssmap_header *) msgb->l3h; + if (bs->length < length - sizeof(*bs)) + return -1; + + switch (bs->type) { + case BSSAP_MSG_BSS_MANAGEMENT: + msgb->l4h = &msgb->l3h[sizeof(*bs)]; + bssmap_rcvmsg_udt(msc, msgb, length - sizeof(*bs)); + break; + default: + LOGP(DMSC, LOGL_NOTICE, "Unimplemented msg type: %s\n", + gsm0808_bssmap_name(bs->type)); + } + + return 0; +} + +int bsc_handle_dt1(struct osmo_bsc_sccp_con *conn, + struct msgb *msg, unsigned int len) +{ + if (len < sizeof(struct bssmap_header)) { + LOGP(DMSC, LOGL_ERROR, "The header is too short.\n"); + } + + switch (msg->l3h[0]) { + case BSSAP_MSG_BSS_MANAGEMENT: + msg->l4h = &msg->l3h[sizeof(struct bssmap_header)]; + bssmap_rcvmsg_dt1(conn, msg, len - sizeof(struct bssmap_header)); + break; + case BSSAP_MSG_DTAP: + dtap_rcvmsg(conn, msg, len); + break; + default: + LOGP(DMSC, LOGL_NOTICE, "Unimplemented BSSAP msg type: %s\n", + gsm0808_bssap_name(msg->l3h[0])); + } + + return -1; +} diff --git a/src/osmo-bsc/osmo_bsc_ctrl.c b/src/osmo-bsc/osmo_bsc_ctrl.c new file mode 100644 index 000000000..c23ed2187 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_ctrl.c @@ -0,0 +1,680 @@ +/* (C) 2011 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2011 by Holger Hans Peter Freyther + * (C) 2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/ctrl/control_cmd.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/signal.h> +#include <openbsc/gsm_04_80.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/signal.h> +#include <osmocom/core/talloc.h> + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_connection *msc_con) +{ + struct ctrl_cmd *trap; + struct ctrl_handle *ctrl; + struct bsc_msc_data *msc_data; + + msc_data = (struct bsc_msc_data *) msc_con->write_queue.bfd.data; + ctrl = msc_data->network->ctrl; + + trap = ctrl_cmd_trap(cmd); + if (!trap) { + LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n"); + return; + } + + ctrl_cmd_send_to_all(ctrl, trap); + ctrl_cmd_send(&msc_con->write_queue, trap); + + talloc_free(trap); +} + +CTRL_CMD_DEFINE_RO(msc_connection_status, "msc_connection_status"); +static int msc_connection_status = 0; + +static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data) +{ + if (msc_connection_status) + cmd->reply = "connected"; + else + cmd->reply = "disconnected"; + return CTRL_CMD_REPLY; +} + +static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +{ + struct ctrl_cmd *cmd; + struct gsm_network *gsmnet = (struct gsm_network *)handler_data; + + if (signal == S_MSC_LOST && msc_connection_status == 1) { + LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n"); + msc_connection_status = 0; + } else if (signal == S_MSC_CONNECTED && msc_connection_status == 0) { + LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n"); + msc_connection_status = 1; + } else { + return 0; + } + + cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); + if (!cmd) { + LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n"); + return 0; + } + + cmd->id = "0"; + cmd->variable = "msc_connection_status"; + + get_msc_connection_status(cmd, NULL); + + ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); + + talloc_free(cmd); + + return 0; +} + +CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status"); +static int bts_connection_status = 0; + +static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data) +{ + if (bts_connection_status) + cmd->reply = "connected"; + else + cmd->reply = "disconnected"; + return CTRL_CMD_REPLY; +} + +static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +{ + struct ctrl_cmd *cmd; + struct gsm_network *gsmnet = (struct gsm_network *)handler_data; + struct gsm_bts *bts; + int bts_current_status; + + if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) { + return 0; + } + + bts_current_status = 0; + /* Check if OML on at least one BTS is up */ + llist_for_each_entry(bts, &gsmnet->bts_list, list) { + if (bts->oml_link) { + bts_current_status = 1; + break; + } + } + if (bts_connection_status == 0 && bts_current_status == 1) { + LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n"); + } else if (bts_connection_status == 1 && bts_current_status == 0) { + LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n"); + } else { + return 0; + } + + cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); + if (!cmd) { + LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n"); + return 0; + } + + bts_connection_status = bts_current_status; + + cmd->id = "0"; + cmd->variable = "bts_connection_status"; + + get_bts_connection_status(cmd, NULL); + + ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); + + talloc_free(cmd); + + return 0; +} + +static int get_bts_loc(struct ctrl_cmd *cmd, void *data); + +static void generate_location_state_trap(struct gsm_bts *bts, struct bsc_msc_connection *msc_con) +{ + struct ctrl_cmd *cmd; + const char *oper, *admin, *policy; + + cmd = ctrl_cmd_create(msc_con, CTRL_TYPE_TRAP); + if (!cmd) { + LOGP(DCTRL, LOGL_ERROR, "Failed to create TRAP command.\n"); + return; + } + + cmd->id = "0"; + cmd->variable = talloc_asprintf(cmd, "bts.%i.location-state", bts->nr); + + /* Prepare the location reply */ + cmd->node = bts; + get_bts_loc(cmd, NULL); + + oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts)); + admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts)); + policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts)); + + cmd->reply = talloc_asprintf_append(cmd->reply, + ",%s,%s,%s,%d,%d", + oper, admin, policy, + bts->network->country_code, + bts->network->network_code); + + osmo_bsc_send_trap(cmd, msc_con); + talloc_free(cmd); +} + +void bsc_gen_location_state_trap(struct gsm_bts *bts) +{ + struct bsc_msc_data *msc; + + llist_for_each_entry(msc, &bts->network->bsc_data->mscs, entry) + generate_location_state_trap(bts, msc->msc_con); +} + +static int location_equal(struct bts_location *a, struct bts_location *b) +{ + return ((a->tstamp == b->tstamp) && (a->valid == b->valid) && (a->lat == b->lat) && + (a->lon == b->lon) && (a->height == b->height)); +} + +static void cleanup_locations(struct llist_head *locations) +{ + struct bts_location *myloc, *tmp; + int invalpos = 0, i = 0; + + LOGP(DCTRL, LOGL_DEBUG, "Checking position list.\n"); + llist_for_each_entry_safe(myloc, tmp, locations, list) { + i++; + if (i > 3) { + LOGP(DCTRL, LOGL_DEBUG, "Deleting old position.\n"); + llist_del(&myloc->list); + talloc_free(myloc); + } else if (myloc->valid == BTS_LOC_FIX_INVALID) { + /* Only capture the newest of subsequent invalid positions */ + invalpos++; + if (invalpos > 1) { + LOGP(DCTRL, LOGL_DEBUG, "Deleting subsequent invalid position.\n"); + invalpos--; + i--; + llist_del(&myloc->list); + talloc_free(myloc); + } + } else { + invalpos = 0; + } + } + LOGP(DCTRL, LOGL_DEBUG, "Found %i positions.\n", i); +} + +CTRL_CMD_DEFINE(bts_loc, "location"); +static int get_bts_loc(struct ctrl_cmd *cmd, void *data) +{ + struct bts_location *curloc; + struct gsm_bts *bts = (struct gsm_bts *) cmd->node; + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + if (llist_empty(&bts->loc_list)) { + cmd->reply = talloc_asprintf(cmd, "0,invalid,0,0,0"); + return CTRL_CMD_REPLY; + } else { + curloc = llist_entry(bts->loc_list.next, struct bts_location, list); + } + + cmd->reply = talloc_asprintf(cmd, "%lu,%s,%f,%f,%f", curloc->tstamp, + get_value_string(bts_loc_fix_names, curloc->valid), curloc->lat, curloc->lon, curloc->height); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_bts_loc(struct ctrl_cmd *cmd, void *data) +{ + char *saveptr, *lat, *lon, *height, *tstamp, *valid, *tmp; + struct bts_location *curloc, *lastloc; + int ret; + struct gsm_bts *bts = (struct gsm_bts *) cmd->node; + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; + + curloc = talloc_zero(tall_bsc_ctx, struct bts_location); + if (!curloc) { + talloc_free(tmp); + goto oom; + } + INIT_LLIST_HEAD(&curloc->list); + + + tstamp = strtok_r(tmp, ",", &saveptr); + valid = strtok_r(NULL, ",", &saveptr); + lat = strtok_r(NULL, ",", &saveptr); + lon = strtok_r(NULL, ",", &saveptr); + height = strtok_r(NULL, "\0", &saveptr); + + curloc->tstamp = atol(tstamp); + curloc->valid = get_string_value(bts_loc_fix_names, valid); + curloc->lat = atof(lat); + curloc->lon = atof(lon); + curloc->height = atof(height); + talloc_free(tmp); + + lastloc = llist_entry(bts->loc_list.next, struct bts_location, list); + + /* Add location to the end of the list */ + llist_add(&curloc->list, &bts->loc_list); + + ret = get_bts_loc(cmd, data); + + if (!location_equal(curloc, lastloc)) + bsc_gen_location_state_trap(bts); + + cleanup_locations(&bts->loc_list); + + return ret; + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} + +static int verify_bts_loc(struct ctrl_cmd *cmd, const char *value, void *data) +{ + char *saveptr, *latstr, *lonstr, *heightstr, *tstampstr, *validstr, *tmp; + time_t tstamp; + int valid; + double lat, lon, height __attribute__((unused)); + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + tstampstr = strtok_r(tmp, ",", &saveptr); + validstr = strtok_r(NULL, ",", &saveptr); + latstr = strtok_r(NULL, ",", &saveptr); + lonstr = strtok_r(NULL, ",", &saveptr); + heightstr = strtok_r(NULL, "\0", &saveptr); + + if ((tstampstr == NULL) || (validstr == NULL) || (latstr == NULL) || + (lonstr == NULL) || (heightstr == NULL)) + goto err; + + tstamp = atol(tstampstr); + valid = get_string_value(bts_loc_fix_names, validstr); + lat = atof(latstr); + lon = atof(lonstr); + height = atof(heightstr); + talloc_free(tmp); + tmp = NULL; + + if (((tstamp == 0) && (valid != BTS_LOC_FIX_INVALID)) || (lat < -90) || (lat > 90) || + (lon < -180) || (lon > 180) || (valid < 0)) { + goto err; + } + + return 0; + +err: + talloc_free(tmp); + cmd->reply = talloc_strdup(cmd, "The format is <unixtime>,(invalid|fix2d|fix3d),<lat>,<lon>,<height>"); + return 1; +} + +CTRL_CMD_DEFINE(net_timezone, "timezone"); +static int get_net_timezone(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = (struct gsm_network*)cmd->node; + + struct gsm_tz *tz = &net->tz; + if (tz->override) + cmd->reply = talloc_asprintf(cmd, "%d,%d,%d", + tz->hr, tz->mn, tz->dst); + else + cmd->reply = talloc_asprintf(cmd, "off"); + + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_net_timezone(struct ctrl_cmd *cmd, void *data) +{ + char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0; + int override; + struct gsm_network *net = (struct gsm_network*)cmd->node; + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; + + hourstr = strtok_r(tmp, ",", &saveptr); + minstr = strtok_r(NULL, ",", &saveptr); + dststr = strtok_r(NULL, ",", &saveptr); + + override = 0; + + if (hourstr != NULL) + override = strcasecmp(hourstr, "off") != 0; + + struct gsm_tz *tz = &net->tz; + tz->override = override; + + if (override) { + tz->hr = hourstr ? atol(hourstr) : 0; + tz->mn = minstr ? atol(minstr) : 0; + tz->dst = dststr ? atol(dststr) : 0; + } + + talloc_free(tmp); + tmp = NULL; + + return get_net_timezone(cmd, data); + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} + +static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data) +{ + char *saveptr, *hourstr, *minstr, *dststr, *tmp; + int override, tz_hours, tz_mins, tz_dst; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + hourstr = strtok_r(tmp, ",", &saveptr); + minstr = strtok_r(NULL, ",", &saveptr); + dststr = strtok_r(NULL, ",", &saveptr); + + if (hourstr == NULL) + goto err; + + override = strcasecmp(hourstr, "off") != 0; + + if (!override) { + talloc_free(tmp); + return 0; + } + + if (minstr == NULL || dststr == NULL) + goto err; + + tz_hours = atol(hourstr); + tz_mins = atol(minstr); + tz_dst = atol(dststr); + + talloc_free(tmp); + tmp = NULL; + + if ((tz_hours < -19) || (tz_hours > 19) || + (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) || + (tz_dst < 0) || (tz_dst > 2)) + goto err; + + return 0; + +err: + talloc_free(tmp); + cmd->reply = talloc_strdup(cmd, "The format is <hours>,<mins>,<dst> or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2"); + return 1; +} + +CTRL_CMD_DEFINE(net_notification, "notification"); +static int get_net_notification(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = "There is nothing to read"; + return CTRL_CMD_ERROR; +} + +static int set_net_notification(struct ctrl_cmd *cmd, void *data) +{ + struct ctrl_cmd *trap; + struct gsm_network *net; + + net = cmd->node; + + trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); + if (!trap) { + LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n"); + goto handled; + } + + trap->id = "0"; + trap->variable = "notification"; + trap->reply = talloc_strdup(trap, cmd->value); + + /* + * This should only be sent to local systems. In the future + * we might even ask for systems to register to receive + * the notifications. + */ + ctrl_cmd_send_to_all(net->ctrl, trap); + talloc_free(trap); + +handled: + return CTRL_CMD_HANDLED; +} + +static int verify_net_notification(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return 0; +} + +CTRL_CMD_DEFINE(net_inform_msc, "inform-msc-v1"); +static int get_net_inform_msc(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = "There is nothing to read"; + return CTRL_CMD_ERROR; +} + +static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net; + struct bsc_msc_data *msc; + + net = cmd->node; + llist_for_each_entry(msc, &net->bsc_data->mscs, entry) { + struct ctrl_cmd *trap; + + trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); + if (!trap) { + LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n"); + continue; + } + + trap->id = "0"; + trap->variable = "inform-msc-v1"; + trap->reply = talloc_strdup(trap, cmd->value); + ctrl_cmd_send(&msc->msc_con->write_queue, trap); + talloc_free(trap); + } + + + return CTRL_CMD_HANDLED; +} + +static int verify_net_inform_msc(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return 0; +} + +CTRL_CMD_DEFINE(net_ussd_notify, "ussd-notify-v1"); +static int get_net_ussd_notify(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = "There is nothing to read"; + return CTRL_CMD_ERROR; +} + +static int set_net_ussd_notify(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_subscriber_connection *conn; + struct gsm_network *net; + char *saveptr = NULL; + char *cic_str, *alert_str, *text_str; + int cic, alert; + + /* Verify has done the test for us */ + cic_str = strtok_r(cmd->value, ",", &saveptr); + alert_str = strtok_r(NULL, ",", &saveptr); + text_str = strtok_r(NULL, ",", &saveptr); + + if (!cic_str || !alert_str || !text_str) { + cmd->reply = "Programming issue. How did this pass verify?"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "No connection found"; + + cic = atoi(cic_str); + alert = atoi(alert_str); + + net = cmd->node; + llist_for_each_entry(conn, &net->subscr_conns, entry) { + if (!conn->sccp_con) + continue; + + if (conn->sccp_con->cic != cic) + continue; + + /* + * This is a hack. My E71 does not like to immediately + * receive a release complete on a TCH. So schedule a + * release complete to clear any previous attempt. The + * right thing would be to track invokeId and only send + * the release complete when we get a returnResultLast + * for this invoke id. + */ + bsc_send_ussd_release_complete(conn); + bsc_send_ussd_notify(conn, alert, text_str); + cmd->reply = "Found a connection"; + break; + } + + return CTRL_CMD_REPLY; +} + +static int verify_net_ussd_notify(struct ctrl_cmd *cmd, const char *value, void *data) +{ + char *saveptr = NULL; + char *inp, *cic, *alert, *text; + + OSMO_ASSERT(cmd); + inp = talloc_strdup(cmd, value); + + cic = strtok_r(inp, ",", &saveptr); + alert = strtok_r(NULL, ",", &saveptr); + text = strtok_r(NULL, ",", &saveptr); + + talloc_free(inp); + if (!cic || !alert || !text) + return 1; + return 0; +} + +static int msc_signal_handler(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct msc_signal_data *msc; + struct gsm_network *net; + struct gsm_bts *bts; + + if (subsys != SS_MSC) + return 0; + if (signal != S_MSC_AUTHENTICATED) + return 0; + + msc = signal_data; + + net = msc->data->network; + llist_for_each_entry(bts, &net->bts_list, list) + generate_location_state_trap(bts, msc->data->msc_con); + + return 0; +} + +int bsc_ctrl_cmds_install(struct gsm_network *net) +{ + int rc; + + rc = bsc_base_ctrl_cmds_install(); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_loc); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc_connection_status); + if (rc) + goto end; + rc = osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net); + if (rc) + goto end; + rc = osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc); + if (rc) + goto end; + rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_ussd_notify); + if (rc) + goto end; + rc = osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net); + +end: + return rc; +} diff --git a/src/osmo-bsc/osmo_bsc_filter.c b/src/osmo-bsc/osmo_bsc_filter.c new file mode 100644 index 000000000..2c84b169f --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_filter.c @@ -0,0 +1,381 @@ +/* (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/osmo_bsc.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/gsm_subscriber.h> +#include <openbsc/bsc_subscriber.h> +#include <openbsc/debug.h> +#include <openbsc/paging.h> + +#include <stdlib.h> + +static void handle_lu_request(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh; + struct gsm48_loc_upd_req *lu; + struct gsm48_loc_area_id lai; + struct gsm_network *net; + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) { + LOGP(DMSC, LOGL_ERROR, "LU too small to look at: %u\n", msgb_l3len(msg)); + return; + } + + net = conn->bts->network; + + gh = msgb_l3(msg); + lu = (struct gsm48_loc_upd_req *) gh->data; + + gsm48_generate_lai(&lai, net->country_code, net->network_code, + conn->bts->location_area_code); + + if (memcmp(&lai, &lu->lai, sizeof(lai)) != 0) { + LOGP(DMSC, LOGL_DEBUG, "Marking con for welcome USSD.\n"); + conn->sccp_con->new_subscriber = 1; + } +} + +/* extract a subscriber from the paging response */ +static struct bsc_subscr *extract_sub(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + uint8_t mi_type; + char mi_string[GSM48_MI_SIZE]; + struct gsm48_hdr *gh; + struct gsm48_pag_resp *resp; + struct bsc_subscr *subscr; + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*resp)) { + LOGP(DMSC, LOGL_ERROR, "PagingResponse too small: %u\n", msgb_l3len(msg)); + return NULL; + } + + gh = msgb_l3(msg); + resp = (struct gsm48_pag_resp *) &gh->data[0]; + + gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh), + mi_string, &mi_type); + DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n", + gsm48_mi_type_name(mi_type), mi_string); + + switch (mi_type) { + case GSM_MI_TYPE_TMSI: + subscr = bsc_subscr_find_by_tmsi(conn->network->bsc_subscribers, + tmsi_from_string(mi_string)); + break; + case GSM_MI_TYPE_IMSI: + subscr = bsc_subscr_find_by_imsi(conn->network->bsc_subscribers, + mi_string); + break; + default: + subscr = NULL; + break; + } + + return subscr; +} + +/* we will need to stop the paging request */ +static int handle_page_resp(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct bsc_subscr *subscr = extract_sub(conn, msg); + + if (!subscr) { + LOGP(DMSC, LOGL_ERROR, "Non active subscriber got paged.\n"); + return -1; + } + + paging_request_stop(&conn->network->bts_list, conn->bts, subscr, conn, + msg); + bsc_subscr_put(subscr); + return 0; +} + +static int is_cm_service_for_emerg(struct msgb *msg) +{ + struct gsm48_service_request *cm; + struct gsm48_hdr *gh = msgb_l3(msg); + + if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*cm)) { + LOGP(DMSC, LOGL_ERROR, "CM ServiceRequest does not fit.\n"); + return 0; + } + + cm = (struct gsm48_service_request *) &gh->data[0]; + return cm->cm_service_type == GSM48_CMSERV_EMERGENCY; +} + +struct bsc_msc_data *bsc_find_msc(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh; + int8_t pdisc; + uint8_t mtype; + struct osmo_bsc_data *bsc; + struct bsc_msc_data *msc, *pag_msc; + struct bsc_subscr *subscr; + int is_emerg = 0; + + bsc = conn->bts->network->bsc_data; + + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "There is no GSM48 header here.\n"); + return NULL; + } + + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + mtype = gsm48_hdr_msg_type(gh); + + /* + * We are asked to select a MSC here but they are not equal. We + * want to respond to a paging request on the MSC where we got the + * request from. This is where we need to decide where this connection + * will go. + */ + if (pdisc == GSM48_PDISC_RR && mtype == GSM48_MT_RR_PAG_RESP) + goto paging; + else if (pdisc == GSM48_PDISC_MM && mtype == GSM48_MT_MM_CM_SERV_REQ) { + is_emerg = is_cm_service_for_emerg(msg); + goto round_robin; + } else + goto round_robin; + +round_robin: + llist_for_each_entry(msc, &bsc->mscs, entry) { + if (!msc->msc_con->is_authenticated) + continue; + if (!is_emerg && msc->type != MSC_CON_TYPE_NORMAL) + continue; + if (is_emerg && !msc->allow_emerg) + continue; + + /* force round robin by moving it to the end */ + llist_move_tail(&msc->entry, &bsc->mscs); + return msc; + } + + return NULL; + +paging: + subscr = extract_sub(conn, msg); + + if (!subscr) { + LOGP(DMSC, LOGL_ERROR, "Got paged but no subscriber found.\n"); + return NULL; + } + + pag_msc = paging_get_data(conn->bts, subscr); + bsc_subscr_put(subscr); + + llist_for_each_entry(msc, &bsc->mscs, entry) { + if (msc != pag_msc) + continue; + + /* + * We don't check if the MSC is connected. In case it + * is not the connection will be dropped. + */ + + /* force round robin by moving it to the end */ + llist_move_tail(&msc->entry, &bsc->mscs); + return msc; + } + + LOGP(DMSC, LOGL_ERROR, "Got paged but no request found.\n"); + return NULL; +} + + +/** + * This is used to scan a message for extra functionality of the BSC. This + * includes scanning for location updating requests/acceptd and then send + * a welcome USSD message to the subscriber. + */ +int bsc_scan_bts_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t pdisc = gsm48_hdr_pdisc(gh); + uint8_t mtype = gsm48_hdr_msg_type(gh); + + if (pdisc == GSM48_PDISC_MM) { + if (mtype == GSM48_MT_MM_LOC_UPD_REQUEST) + handle_lu_request(conn, msg); + } else if (pdisc == GSM48_PDISC_RR) { + if (mtype == GSM48_MT_RR_PAG_RESP) + handle_page_resp(conn, msg); + } + + return 0; +} + +static int send_welcome_ussd(struct gsm_subscriber_connection *conn) +{ + struct osmo_bsc_sccp_con *bsc_con; + + bsc_con = conn->sccp_con; + if (!bsc_con) { + LOGP(DMSC, LOGL_DEBUG, "No SCCP connection associated.\n"); + return 0; + } + + if (!bsc_con->msc->ussd_welcome_txt) { + LOGP(DMSC, LOGL_DEBUG, "No USSD Welcome text defined.\n"); + return 0; + } + + return BSS_SEND_USSD; +} + +int bsc_send_welcome_ussd(struct gsm_subscriber_connection *conn) +{ + bsc_send_ussd_notify(conn, 1, conn->sccp_con->msc->ussd_welcome_txt); + bsc_send_ussd_release_complete(conn); + + return 0; +} + +static int bsc_patch_mm_info(struct gsm_subscriber_connection *conn, + uint8_t *data, unsigned int length) +{ + struct tlv_parsed tp; + int parse_res; + struct gsm_bts *bts = conn->bts; + int tzunits; + uint8_t tzbsd = 0; + uint8_t dst = 0; + + parse_res = tlv_parse(&tp, &gsm48_mm_att_tlvdef, data, length, 0, 0); + if (parse_res <= 0 && parse_res != -3) + /* FIXME: -3 means unknown IE error, so this accepts messages + * with unknown IEs. But parsing has aborted with the unknown + * IE and the message is broken or parsed incompletely. */ + return 0; + + /* Is TZ patching enabled? */ + struct gsm_tz *tz = &bts->network->tz; + if (!tz->override) + return 0; + + /* Convert tz.hr and tz.mn to units */ + if (tz->hr < 0) { + tzunits = -tz->hr*4; + tzbsd |= 0x08; + } else + tzunits = tz->hr*4; + + tzunits = tzunits + (tz->mn/15); + + tzbsd |= (tzunits % 10)*0x10 + (tzunits / 10); + + /* Convert DST value */ + if (tz->dst >= 0 && tz->dst <= 2) + dst = tz->dst; + + if (TLVP_PRESENT(&tp, GSM48_IE_UTC)) { + LOGP(DMSC, LOGL_DEBUG, + "Changing 'Local time zone' from 0x%02x to 0x%02x.\n", + TLVP_VAL(&tp, GSM48_IE_UTC)[6], tzbsd); + ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_UTC)))[0] = tzbsd; + } + if (TLVP_PRESENT(&tp, GSM48_IE_NET_TIME_TZ)) { + LOGP(DMSC, LOGL_DEBUG, + "Changing 'Universal time and local time zone' TZ from " + "0x%02x to 0x%02x.\n", + TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)[6], tzbsd); + ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_TIME_TZ)))[6] = tzbsd; + } +#ifdef GSM48_IE_NET_DST + if (TLVP_PRESENT(&tp, GSM48_IE_NET_DST)) { + LOGP(DMSC, LOGL_DEBUG, + "Changing 'Network daylight saving time' from " + "0x%02x to 0x%02x.\n", + TLVP_VAL(&tp, GSM48_IE_NET_DST)[0], dst); + ((uint8_t *)(TLVP_VAL(&tp, GSM48_IE_NET_DST)))[0] = dst; + } +#endif + + return 0; +} + +static int has_core_identity(struct bsc_msc_data *msc) +{ + if (msc->core_mnc != -1) + return 1; + if (msc->core_mcc != -1) + return 1; + if (msc->core_lac != -1) + return 1; + if (msc->core_ci != -1) + return 1; + return 0; +} + +/** + * Messages coming back from the MSC. + */ +int bsc_scan_msc_msg(struct gsm_subscriber_connection *conn, struct msgb *msg) +{ + struct bsc_msc_data *msc; + struct gsm_network *net; + struct gsm48_loc_area_id *lai; + struct gsm48_hdr *gh; + uint8_t pdisc; + uint8_t mtype; + int length = msgb_l3len(msg); + + if (length < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "GSM48 header does not fit.\n"); + return -1; + } + + gh = (struct gsm48_hdr *) msgb_l3(msg); + length -= (const char *)&gh->data[0] - (const char *)gh; + + pdisc = gsm48_hdr_pdisc(gh); + if (pdisc != GSM48_PDISC_MM) + return 0; + + mtype = gsm48_hdr_msg_type(gh); + net = conn->bts->network; + msc = conn->sccp_con->msc; + + if (mtype == GSM48_MT_MM_LOC_UPD_ACCEPT) { + if (has_core_identity(msc)) { + if (msgb_l3len(msg) >= sizeof(*gh) + sizeof(*lai)) { + /* overwrite LAI in the message */ + lai = (struct gsm48_loc_area_id *) &gh->data[0]; + gsm48_generate_lai(lai, net->country_code, + net->network_code, + conn->bts->location_area_code); + } + } + + if (conn->sccp_con->new_subscriber) + return send_welcome_ussd(conn); + return 0; + } else if (mtype == GSM48_MT_MM_INFO) { + bsc_patch_mm_info(conn, &gh->data[0], length); + } + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_grace.c b/src/osmo-bsc/osmo_bsc_grace.c new file mode 100644 index 000000000..63afa20d0 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_grace.c @@ -0,0 +1,169 @@ +/* + * (C) 2010-2013 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010-2013 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/osmo_bsc_grace.h> +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/bsc_subscriber.h> +#include <openbsc/paging.h> +#include <openbsc/signal.h> + +int bsc_grace_allow_new_connection(struct gsm_network *network, struct gsm_bts *bts) +{ + if (bts->excl_from_rf_lock) + return 1; + return network->bsc_data->rf_ctrl->policy == S_RF_ON; +} + + +static int normal_paging(struct bsc_subscr *subscr, int chan_needed, + struct bsc_msc_data *msc) +{ + /* we can't page by lac.. we need to page everything */ + if (msc->core_lac != -1) { + struct gsm_bts *bts; + + llist_for_each_entry(bts, &msc->network->bts_list, list) + paging_request_bts(bts, subscr, chan_needed, NULL, msc); + + return 0; + } + + return paging_request(msc->network, subscr, chan_needed, NULL, msc); +} + +static int locked_paging(struct bsc_subscr *subscr, int chan_needed, + struct bsc_msc_data *msc) +{ + struct gsm_bts *bts = NULL; + + /* + * Check if there is any BTS that is on for the given lac. Start + * with NULL and iterate through all bts. + */ + llist_for_each_entry(bts, &msc->network->bts_list, list) { + /* + * continue if the BTS is not excluded from the lock + */ + if (!bts->excl_from_rf_lock) + continue; + + /* in case of no lac patching is in place, check the BTS */ + if (msc->core_lac == -1 && subscr->lac != bts->location_area_code) + continue; + + /* + * now page on this bts + */ + paging_request_bts(bts, subscr, chan_needed, NULL, msc); + }; + + /* All bts are either off or in the grace period */ + return 0; +} + +/** + * Try to not page if everything the cell is not on. + */ +int bsc_grace_paging_request(enum signal_rf rf_policy, + struct bsc_subscr *subscr, + int chan_needed, + struct bsc_msc_data *msc) +{ + if (rf_policy == S_RF_ON) + return normal_paging(subscr, chan_needed, msc); + return locked_paging(subscr, chan_needed, msc); +} + +static int handle_sub(struct gsm_lchan *lchan, const char *text) +{ + struct gsm_subscriber_connection *conn; + + /* only send it to TCH */ + if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F) + return -1; + + /* only send on the primary channel */ + conn = lchan->conn; + if (!conn) + return -1; + + if (conn->lchan != lchan) + return -1; + + /* only when active */ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + bsc_send_ussd_notify(conn, 0, text); + bsc_send_ussd_release_complete(conn); + + return 0; +} + +/* + * The place to handle the grace mode. Right now we will send + * USSD messages to the subscriber, in the future we might start + * a timer to have different modes for the grace period. + */ +static int handle_grace(struct gsm_network *network) +{ + int ts_nr, lchan_nr; + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + + if (!network->bsc_data->mid_call_txt) + return 0; + + llist_for_each_entry(bts, &network->bts_list, list) { + llist_for_each_entry(trx, &bts->trx_list, list) { + for (ts_nr = 0; ts_nr < TRX_NR_TS; ++ts_nr) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; ++lchan_nr) { + handle_sub(&ts->lchan[lchan_nr], + network->bsc_data->mid_call_txt); + } + } + } + } + return 0; +} + +static int handle_rf_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct rf_signal_data *sig; + + if (subsys != SS_RF) + return -1; + + sig = signal_data; + + if (signal == S_RF_GRACE) + handle_grace(sig->net); + + return 0; +} + +static __attribute__((constructor)) void on_dso_load_grace(void) +{ + osmo_signal_register_handler(SS_RF, handle_rf_signal, NULL); +} diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c new file mode 100644 index 000000000..ee094d670 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -0,0 +1,301 @@ +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2011 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/bss.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/osmo_bsc_rf.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/signal.h> +#include <openbsc/vty.h> +#include <openbsc/ipaccess.h> +#include <openbsc/ctrl.h> + +#include <osmocom/ctrl/control_cmd.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/ports.h> +#include <osmocom/ctrl/control_vty.h> + +#include <osmocom/core/application.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/stats.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmocom/abis/abis.h> + +#include <osmocom/sccp/sccp.h> + +#define _GNU_SOURCE +#include <getopt.h> + +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + + +#include "../../bscconfig.h" + +struct gsm_network *bsc_gsmnet = 0; +static const char *config_file = "openbsc.cfg"; +static const char *rf_ctrl = NULL; +extern const char *openbsc_copyright; +static int daemonize = 0; +static struct llist_head access_lists; + +struct llist_head *bsc_access_lists(void) +{ + return &access_lists; +} + +static void print_usage() +{ + printf("Usage: osmo-bsc\n"); +} + +static void print_help() +{ + printf(" Some useful help...\n"); + printf(" -h --help this text\n"); + printf(" -D --daemonize Fork the process into a background daemon\n"); + printf(" -d option --debug=DRLL:DCC:DMM:DRR:DRSL:DNM enable debugging\n"); + printf(" -s --disable-color\n"); + printf(" -T --timestamp. Print a timestamp in the debug output.\n"); + printf(" -c --config-file filename The config file to use.\n"); + printf(" -l --local=IP. The local address of the MGCP.\n"); + printf(" -e --log-level number. Set a global loglevel.\n"); + printf(" -r --rf-ctl NAME. A unix domain socket to listen for cmds.\n"); + printf(" -t --testmode. A special mode to provoke failures at the MSC.\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'}, + {"timestamp", 0, 0, 'T'}, + {"local", 1, 0, 'l'}, + {"log-level", 1, 0, 'e'}, + {"rf-ctl", 1, 0, 'r'}, + {"testmode", 0, 0, 't'}, + {0, 0, 0, 0} + }; + + c = getopt_long(argc, argv, "hd:DsTc:e:r:t", + 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 'c': + config_file = optarg; + break; + case 'T': + log_set_print_timestamp(osmo_stderr_target, 1); + break; + case 'e': + log_set_log_level(osmo_stderr_target, atoi(optarg)); + break; + case 'r': + rf_ctrl = optarg; + break; + default: + /* ignore */ + break; + } + } +} + +extern int bsc_vty_go_parent(struct vty *vty); + +static struct vty_app_info vty_info = { + .name = "OsmoBSC", + .version = PACKAGE_VERSION, + .go_parent_cb = bsc_vty_go_parent, + .is_config_node = bsc_vty_is_config_node, +}; + +extern int bsc_shutdown_net(struct gsm_network *net); +static void signal_handler(int signal) +{ + struct bsc_msc_data *msc; + + 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: + /* 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: + if (!bsc_gsmnet->bsc_data) + return; + llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) + bsc_msc_lost(msc->msc_con); + break; + default: + break; + } +} + +int main(int argc, char **argv) +{ + struct bsc_msc_data *msc; + struct osmo_bsc_data *data; + int rc; + + tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc"); + msgb_talloc_ctx_init(tall_bsc_ctx, 0); + + /* Allocate global gsm_network struct */ + rc = bsc_network_alloc(NULL); + if (rc) { + fprintf(stderr, "Allocation failed. exiting.\n"); + exit(1); + } + + osmo_init_logging(&log_info); + osmo_stats_init(tall_bsc_ctx); + + bts_init(); + libosmo_abis_init(tall_bsc_ctx); + + /* enable filters */ + + /* This needs to precede handle_options() */ + vty_info.copyright = openbsc_copyright; + vty_init(&vty_info); + bsc_vty_init(bsc_gsmnet); + bsc_msg_lst_vty_init(tall_bsc_ctx, &access_lists, BSC_NODE); + ctrl_vty_init(tall_bsc_ctx); + + INIT_LLIST_HEAD(&access_lists); + + /* parse options */ + handle_options(argc, argv); + + /* seed the PRNG */ + srand(time(NULL)); + + /* initialize SCCP */ + sccp_set_log_area(DSCCP); + + /* Read the config */ + rc = bsc_network_configure(config_file); + if (rc < 0) { + fprintf(stderr, "Bootstrapping the network failed. exiting.\n"); + exit(1); + } + bsc_api_init(bsc_gsmnet, osmo_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) { + fprintf(stderr, "Failed to init the control interface. Exiting.\n"); + exit(1); + } + + rc = bsc_ctrl_cmds_install(bsc_gsmnet); + if (rc < 0) { + fprintf(stderr, "Failed to install control commands. Exiting.\n"); + exit(1); + } + + data = bsc_gsmnet->bsc_data; + if (rf_ctrl) + osmo_talloc_replace_string(data, &data->rf_ctrl_name, rf_ctrl); + + data->rf_ctrl = osmo_bsc_rf_create(data->rf_ctrl_name, bsc_gsmnet); + if (!data->rf_ctrl) { + fprintf(stderr, "Failed to create the RF service.\n"); + exit(1); + } + + llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) { + if (osmo_bsc_msc_init(msc) != 0) { + LOGP(DNAT, LOGL_ERROR, "Failed to start up. Exiting.\n"); + exit(1); + } + } + + + if (osmo_bsc_sccp_init(bsc_gsmnet) != 0) { + LOGP(DNM, LOGL_ERROR, "Failed to register SCCP.\n"); + exit(1); + } + + if (osmo_bsc_audio_init(bsc_gsmnet) != 0) { + LOGP(DMSC, LOGL_ERROR, "Failed to register audio support.\n"); + exit(1); + } + + signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + osmo_init_ignore_signals(); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + osmo_select_main(0); + } + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_msc.c b/src/osmo-bsc/osmo_bsc_msc.c new file mode 100644 index 000000000..8d02624b4 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_msc.c @@ -0,0 +1,586 @@ +/* + * Handle the connection to the MSC. This include ping/timeout/reconnect + * (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2015 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2015 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/bsc_nat.h> +#include <osmocom/ctrl/control_cmd.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/crypt/auth.h> +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/ipaccess.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/signal.h> + +#include <osmocom/core/talloc.h> + +#include <osmocom/gsm/gsm0808.h> + +#include <osmocom/sccp/sccp.h> + +#include <osmocom/abis/ipa.h> + +#include <sys/socket.h> +#include <netinet/tcp.h> +#include <unistd.h> + + +static void initialize_if_needed(struct bsc_msc_connection *conn); +static void send_lacs(struct gsm_network *net, struct bsc_msc_connection *conn); +static void send_id_get_response(struct bsc_msc_data *data, int fd, struct msgb *inp); +static void send_ping(struct bsc_msc_data *data); +static void schedule_ping_pong(struct bsc_msc_data *data); + +/* + * MGCP forwarding code + */ +static int mgcp_do_read(struct osmo_fd *fd) +{ + struct bsc_msc_data *data = (struct bsc_msc_data *) fd->data; + struct msgb *mgcp; + int ret; + + mgcp = msgb_alloc_headroom(4096, 128, "mgcp_from_gw"); + if (!mgcp) { + LOGP(DMGCP, LOGL_ERROR, "Failed to allocate MGCP message.\n"); + return -1; + } + + ret = read(fd->fd, mgcp->data, 4096 - 128); + if (ret <= 0) { + LOGP(DMGCP, LOGL_ERROR, "Failed to read: %d/%s\n", errno, strerror(errno)); + msgb_free(mgcp); + return -1; + } else if (ret > 4096 - 128) { + LOGP(DMGCP, LOGL_ERROR, "Too much data: %d\n", ret); + msgb_free(mgcp); + return -1; + } + + mgcp->l2h = msgb_put(mgcp, ret); + msc_queue_write(data->msc_con, mgcp, IPAC_PROTO_MGCP_OLD); + return 0; +} + +static int mgcp_do_write(struct osmo_fd *fd, struct msgb *msg) +{ + int ret; + + 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; +} + +static void mgcp_forward(struct bsc_msc_data *data, struct msgb *msg) +{ + struct msgb *mgcp; + + if (msgb_l2len(msg) > 4096) { + LOGP(DMGCP, LOGL_ERROR, "Can not forward too big message.\n"); + return; + } + + mgcp = msgb_alloc(4096, "mgcp_to_gw"); + if (!mgcp) { + LOGP(DMGCP, LOGL_ERROR, "Failed to send message.\n"); + return; + } + + msgb_put(mgcp, msgb_l2len(msg)); + memcpy(mgcp->data, msg->l2h, mgcp->len); + if (osmo_wqueue_enqueue(&data->mgcp_agent, mgcp) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Could not queue message to MGCP GW.\n"); + msgb_free(mgcp); + } +} + +static int mgcp_create_port(struct bsc_msc_data *data) +{ + int on; + struct sockaddr_in addr; + + data->mgcp_agent.bfd.fd = socket(AF_INET, SOCK_DGRAM, 0); + if (data->mgcp_agent.bfd.fd < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to create UDP socket errno: %d\n", errno); + return -1; + } + + on = 1; + setsockopt(data->mgcp_agent.bfd.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + /* try to bind the socket */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + if (bind(data->mgcp_agent.bfd.fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to bind to any port.\n"); + close(data->mgcp_agent.bfd.fd); + data->mgcp_agent.bfd.fd = -1; + return -1; + } + + /* connect to the remote */ + addr.sin_port = htons(2427); + if (connect(data->mgcp_agent.bfd.fd, (struct sockaddr *) & addr, sizeof(addr)) < 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to connect to local MGCP GW. %s\n", strerror(errno)); + close(data->mgcp_agent.bfd.fd); + data->mgcp_agent.bfd.fd = -1; + return -1; + } + + osmo_wqueue_init(&data->mgcp_agent, 10); + data->mgcp_agent.bfd.when = BSC_FD_READ; + data->mgcp_agent.bfd.data = data; + data->mgcp_agent.read_cb = mgcp_do_read; + data->mgcp_agent.write_cb = mgcp_do_write; + + if (osmo_fd_register(&data->mgcp_agent.bfd) != 0) { + LOGP(DMGCP, LOGL_FATAL, "Failed to register BFD\n"); + close(data->mgcp_agent.bfd.fd); + data->mgcp_agent.bfd.fd = -1; + return -1; + } + + return 0; +} + +/* + * Send data to the network + */ +int msc_queue_write(struct bsc_msc_connection *conn, struct msgb *msg, int proto) +{ + ipa_prepend_header(msg, proto); + if (osmo_wqueue_enqueue(&conn->write_queue, msg) != 0) { + LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto); + msgb_free(msg); + return -1; + } + + return 0; +} + +int msc_queue_write_with_ping(struct bsc_msc_connection *conn, + struct msgb *msg, int proto) +{ + struct bsc_msc_data *data; + uint8_t val; + + /* prepend the header */ + ipa_prepend_header(msg, proto); + if (osmo_wqueue_enqueue(&conn->write_queue, msg) != 0) { + LOGP(DMSC, LOGL_FATAL, "Failed to queue IPA/%d\n", proto); + msgb_free(msg); + return -1; + } + + /* add the ping as the other message */ + val = IPAC_MSGT_PING; + msgb_l16tv_put(msg, 1, IPAC_PROTO_IPACCESS, &val); + + data = (struct bsc_msc_data *) conn->write_queue.bfd.data; + schedule_ping_pong(data); + return 0; +} + +static int msc_alink_do_write(struct osmo_fd *fd, struct msgb *msg) +{ + int ret; + + LOGP(DMSC, LOGL_DEBUG, "Sending SCCP to MSC: %u\n", msgb_l2len(msg)); + LOGP(DLMI, LOGL_DEBUG, "MSC TX %s\n", osmo_hexdump(msg->data, msg->len)); + + ret = write(fd->fd, msg->data, msg->len); + if (ret < msg->len) + perror("MSC: Failed to send SCCP"); + + return ret; +} + +static void handle_ctrl(struct bsc_msc_data *msc, struct msgb *msg) +{ + int ret; + struct ctrl_cmd *cmd; + + cmd = ctrl_cmd_parse(msc->msc_con, msg); + if (!cmd) { + LOGP(DMSC, LOGL_ERROR, "Failed to parse control message.\n"); + cmd = talloc_zero(msc->msc_con, struct ctrl_cmd); + if (!cmd) { + LOGP(DMSC, LOGL_ERROR, "OOM!\n"); + return; + } + cmd->type = CTRL_TYPE_ERROR; + cmd->id = "err"; + cmd->reply = "Failed to parse control message."; + + ctrl_cmd_send(&msc->msc_con->write_queue, cmd); + talloc_free(cmd); + + return; + } + + ret = ctrl_cmd_handle(msc->network->ctrl, cmd, msc->network); + if (ret != CTRL_CMD_HANDLED) + ctrl_cmd_send(&msc->msc_con->write_queue, cmd); + talloc_free(cmd); +} + +static void osmo_ext_handle(struct bsc_msc_data *msc, struct msgb *msg) +{ + struct ipaccess_head *hh; + struct ipaccess_head_ext *hh_ext; + + hh = (struct ipaccess_head *) msg->data; + hh_ext = (struct ipaccess_head_ext *) hh->data; + if (msg->len < sizeof(*hh) + sizeof(*hh_ext)) { + LOGP(DMSC, LOGL_ERROR, "Packet too short for extended header.\n"); + return; + } + + msg->l2h = hh_ext->data; + if (hh_ext->proto == IPAC_PROTO_EXT_MGCP) + mgcp_forward(msc, msg); + else if (hh_ext->proto == IPAC_PROTO_EXT_LAC) + send_lacs(msc->network, msc->msc_con); + else if (hh_ext->proto == IPAC_PROTO_EXT_CTRL) + handle_ctrl(msc, msg); +} + +static int ipaccess_a_fd_cb(struct osmo_fd *bfd) +{ + struct msgb *msg = NULL; + struct ipaccess_head *hh; + struct bsc_msc_data *data = (struct bsc_msc_data *) bfd->data; + int ret; + + ret = ipa_msg_recv_buffered(bfd->fd, &msg, &data->msc_con->pending_msg); + if (ret <= 0) { + if (ret == -EAGAIN) + return 0; + if (ret == 0) { + LOGP(DMSC, LOGL_ERROR, "The connection to the MSC was lost.\n"); + bsc_msc_lost(data->msc_con); + return -1; + } + + LOGP(DMSC, LOGL_ERROR, "Failed to parse ip access message: %d\n", ret); + return -1; + } + + LOGP(DLMI, LOGL_DEBUG, "From MSC: %s proto: %d\n", osmo_hexdump(msg->data, msg->len), msg->l2h[0]); + + /* handle base message handling */ + hh = (struct ipaccess_head *) msg->data; + + /* initialize the networking. This includes sending a GSM08.08 message */ + msg->cb[0] = (unsigned long) data; + if (hh->proto == IPAC_PROTO_IPACCESS) { + ipa_ccm_rcvmsg_base(msg, bfd); + if (msg->l2h[0] == IPAC_MSGT_ID_ACK) + initialize_if_needed(data->msc_con); + else if (msg->l2h[0] == IPAC_MSGT_ID_GET) { + send_id_get_response(data, bfd->fd, msg); + } else if (msg->l2h[0] == IPAC_MSGT_PONG) { + osmo_timer_del(&data->pong_timer); + } + } else if (hh->proto == IPAC_PROTO_SCCP) { + sccp_system_incoming_ctx(msg, data->msc_con); + } else if (hh->proto == IPAC_PROTO_MGCP_OLD) { + mgcp_forward(data, msg); + } else if (hh->proto == IPAC_PROTO_OSMO) { + osmo_ext_handle(data, msg); + } + + msgb_free(msg); + return 0; +} + +static void send_ping(struct bsc_msc_data *data) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(4096, 128, "ping"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to create PING.\n"); + return; + } + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = IPAC_MSGT_PING; + + msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS); +} + +static void schedule_ping_pong(struct bsc_msc_data *data) +{ + /* send another ping in 20 seconds */ + osmo_timer_schedule(&data->ping_timer, data->ping_timeout, 0); + + /* also start a pong timer */ + osmo_timer_schedule(&data->pong_timer, data->pong_timeout, 0); +} + +static void msc_ping_timeout_cb(void *_data) +{ + struct bsc_msc_data *data = (struct bsc_msc_data *) _data; + if (data->ping_timeout <= 0) + return; + + send_ping(data); + schedule_ping_pong(data); +} + +static void msc_pong_timeout_cb(void *_data) +{ + struct bsc_msc_data *data = (struct bsc_msc_data *) _data; + + LOGP(DMSC, LOGL_ERROR, "MSC didn't answer PING. Closing connection.\n"); + bsc_msc_lost(data->msc_con); +} + +static void msc_connection_connected(struct bsc_msc_connection *con) +{ + struct msc_signal_data sig; + struct bsc_msc_data *data; + int ret, on; + on = 1; + ret = setsockopt(con->write_queue.bfd.fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + if (ret != 0) + LOGP(DMSC, LOGL_ERROR, "Failed to set TCP_NODELAY: %s\n", strerror(errno)); + + data = (struct bsc_msc_data *) con->write_queue.bfd.data; + msc_ping_timeout_cb(data); + + sig.data = data; + osmo_signal_dispatch(SS_MSC, S_MSC_CONNECTED, &sig); +} + +/* + * The connection to the MSC was lost and we will need to free all + * resources and then attempt to reconnect. + */ +static void msc_connection_was_lost(struct bsc_msc_connection *msc) +{ + struct msc_signal_data sig; + struct bsc_msc_data *data; + + LOGP(DMSC, LOGL_ERROR, "Lost MSC connection. Freing stuff.\n"); + + data = (struct bsc_msc_data *) msc->write_queue.bfd.data; + osmo_timer_del(&data->ping_timer); + osmo_timer_del(&data->pong_timer); + + sig.data = data; + osmo_signal_dispatch(SS_MSC, S_MSC_LOST, &sig); + + msc->is_authenticated = 0; + bsc_msc_schedule_connect(msc); +} + +static void send_lacs(struct gsm_network *net, struct bsc_msc_connection *conn) +{ + struct ipac_ext_lac_cmd *lac; + struct gsm_bts *bts; + struct msgb *msg; + int lacs = 0; + + if (llist_empty(&net->bts_list)) { + LOGP(DMSC, LOGL_ERROR, "No BTSs configured. Not sending LACs.\n"); + return; + } + + msg = msgb_alloc_headroom(4096, 128, "LAC Command"); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to create the LAC command.\n"); + return; + } + + lac = (struct ipac_ext_lac_cmd *) msgb_put(msg, sizeof(*lac)); + lac->add_remove = 1; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (lacs++ == 0) + lac->lac = htons(bts->location_area_code); + else + msgb_put_u16(msg, htons(bts->location_area_code)); + } + + lac->nr_extra_lacs = lacs - 1; + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_LAC); + msc_queue_write(conn, msg, IPAC_PROTO_OSMO); +} + +static void initialize_if_needed(struct bsc_msc_connection *conn) +{ + struct msgb *msg; + + if (!conn->is_authenticated) { + /* send a gsm 08.08 reset message from here */ + msg = gsm0808_create_reset(); + if (!msg) { + LOGP(DMSC, LOGL_ERROR, "Failed to create the reset message.\n"); + return; + } + + sccp_write(msg, &sccp_ssn_bssap, &sccp_ssn_bssap, 0, conn); + msgb_free(msg); + conn->is_authenticated = 1; + } +} + +static int answer_challenge(struct bsc_msc_data *data, struct msgb *inp, struct osmo_auth_vector *vec) +{ + int ret; + struct tlv_parsed tvp; + const uint8_t *mrand; + uint8_t mrand_len; + struct osmo_sub_auth_data auth = { + .type = OSMO_AUTH_TYPE_GSM, + .algo = OSMO_AUTH_ALG_MILENAGE, + }; + + ret = ipa_ccm_idtag_parse_off(&tvp, + inp->l2h + 1, + msgb_l2len(inp) - 1, 1); + if (ret < 0) { + LOGP(DMSC, LOGL_ERROR, "ignoring IPA response " + "message with malformed TLVs: %s\n", osmo_hexdump(inp->l2h + 1, + msgb_l2len(inp) - 1)); + return 0; + } + + mrand = TLVP_VAL(&tvp, 0x23); + mrand_len = TLVP_LEN(&tvp, 0x23); + if (mrand_len != 16) { + LOGP(DMSC, LOGL_ERROR, + "RAND is not 16 bytes. Was %d\n", + mrand_len); + return 0; + } + + /* copy the key */ + memcpy(auth.u.umts.opc, data->bsc_key, 16); + memcpy(auth.u.umts.k, data->bsc_key, 16); + memset(auth.u.umts.amf, 0, 2); + auth.u.umts.sqn = 0; + + /* generate the result */ + memset(vec, 0, sizeof(*vec)); + osmo_auth_gen_vec(vec, &auth, mrand); + return 1; +} + + +static void send_id_get_response(struct bsc_msc_data *data, int fd, struct msgb *inp) +{ + struct msc_signal_data sig; + struct msgb *msg; + struct osmo_auth_vector vec; + int valid = 0; + + if (data->bsc_key_present) + valid = answer_challenge(data, inp, &vec); + + msg = bsc_msc_id_get_resp(valid, data->bsc_token, + vec.res, valid ? vec.res_len : 0); + if (!msg) + return; + msc_queue_write(data->msc_con, msg, IPAC_PROTO_IPACCESS); + + sig.data = data; + osmo_signal_dispatch(SS_MSC, S_MSC_AUTHENTICATED, &sig); +} + +int osmo_bsc_msc_init(struct bsc_msc_data *data) +{ + if (mgcp_create_port(data) != 0) + return -1; + + data->msc_con = bsc_msc_create(data, &data->dests); + if (!data->msc_con) { + LOGP(DMSC, LOGL_ERROR, "Creating the MSC network connection failed.\n"); + return -1; + } + + osmo_timer_setup(&data->ping_timer, msc_ping_timeout_cb, data); + osmo_timer_setup(&data->pong_timer, msc_pong_timeout_cb, data); + + data->msc_con->write_queue.bfd.data = data; + data->msc_con->connection_loss = msc_connection_was_lost; + data->msc_con->connected = msc_connection_connected; + data->msc_con->write_queue.read_cb = ipaccess_a_fd_cb; + data->msc_con->write_queue.write_cb = msc_alink_do_write; + bsc_msc_connect(data->msc_con); + + return 0; +} + +struct bsc_msc_data *osmo_msc_data_find(struct gsm_network *net, int nr) +{ + struct bsc_msc_data *msc_data; + + llist_for_each_entry(msc_data, &net->bsc_data->mscs, entry) + if (msc_data->nr == nr) + return msc_data; + return NULL; +} + +struct bsc_msc_data *osmo_msc_data_alloc(struct gsm_network *net, int nr) +{ + struct bsc_msc_data *msc_data; + + /* check if there is already one */ + msc_data = osmo_msc_data_find(net, nr); + if (msc_data) + return msc_data; + + msc_data = talloc_zero(net, struct bsc_msc_data); + if (!msc_data) + return NULL; + + llist_add_tail(&msc_data->entry, &net->bsc_data->mscs); + + /* Init back pointer */ + msc_data->network = net; + + INIT_LLIST_HEAD(&msc_data->dests); + msc_data->ping_timeout = 20; + msc_data->pong_timeout = 5; + msc_data->core_mnc = -1; + msc_data->core_mcc = -1; + msc_data->core_ci = -1; + msc_data->core_lac = -1; + msc_data->rtp_base = 4000; + + msc_data->nr = nr; + msc_data->allow_emerg = 1; + + /* Defaults for the audio setup */ + msc_data->amr_conf.m5_90 = 1; + + return msc_data; +} diff --git a/src/osmo-bsc/osmo_bsc_sccp.c b/src/osmo-bsc/osmo_bsc_sccp.c new file mode 100644 index 000000000..e242390ef --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_sccp.c @@ -0,0 +1,328 @@ +/* Interaction with the SCCP subsystem */ +/* + * (C) 2009-2014 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/gsm_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/osmo_bsc_grace.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/gsm_04_80.h> +#include <openbsc/debug.h> +#include <openbsc/ipaccess.h> +#include <openbsc/signal.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/gsm0808.h> +#include <osmocom/gsm/protocol/gsm_08_08.h> + +#include <osmocom/sccp/sccp.h> + +/* SCCP helper */ +#define SCCP_IT_TIMER 60 + +static LLIST_HEAD(active_connections); + +static void free_queued(struct osmo_bsc_sccp_con *conn) +{ + struct msgb *msg; + + while (!llist_empty(&conn->sccp_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&conn->sccp_queue); + msgb_free(msg); + } + + conn->sccp_queue_size = 0; +} + +static void send_queued(struct osmo_bsc_sccp_con *conn) +{ + struct msgb *msg; + + while (!llist_empty(&conn->sccp_queue)) { + /* this is not allowed to fail */ + msg = msgb_dequeue(&conn->sccp_queue); + sccp_connection_write(conn->sccp, msg); + msgb_free(msg); + conn->sccp_queue_size -= 1; + } +} + +static void msc_outgoing_sccp_data(struct sccp_connection *conn, + struct msgb *msg, unsigned int len) +{ + struct osmo_bsc_sccp_con *bsc_con = + (struct osmo_bsc_sccp_con *) conn->data_ctx; + + bsc_handle_dt1(bsc_con, msg, len); +} + +static void msc_outgoing_sccp_state(struct sccp_connection *conn, int old_state) +{ + struct osmo_bsc_sccp_con *con_data; + + if (conn->connection_state >= SCCP_CONNECTION_STATE_RELEASE_COMPLETE) { + con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx; + if (con_data->conn) { + LOGP(DMSC, LOGL_ERROR, + "ERROR: The lchan is still associated.\n"); + gsm0808_clear(con_data->conn); + bsc_subscr_con_free(con_data->conn); + con_data->conn = NULL; + } + + con_data->sccp = NULL; + free_queued(con_data); + sccp_connection_free(conn); + bsc_delete_connection(con_data); + } else if (conn->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DMSC, LOGL_DEBUG, "Connection established: %p\n", conn); + con_data = (struct osmo_bsc_sccp_con *) conn->data_ctx; + + osmo_timer_del(&con_data->sccp_cc_timeout); + osmo_timer_schedule(&con_data->sccp_it_timeout, SCCP_IT_TIMER, 0); + + send_queued(con_data); + } +} + +static void bsc_sccp_force_free(struct osmo_bsc_sccp_con *data) +{ + if (data->conn) { + gsm0808_clear(data->conn); + bsc_subscr_con_free(data->conn); + data->conn = NULL; + } + + free_queued(data); + sccp_connection_force_free(data->sccp); + data->sccp = NULL; + bsc_delete_connection(data); +} + +static void sccp_it_timeout(void *_data) +{ + struct osmo_bsc_sccp_con *data = + (struct osmo_bsc_sccp_con *) _data; + + sccp_connection_send_it(data->sccp); + osmo_timer_schedule(&data->sccp_it_timeout, SCCP_IT_TIMER, 0); +} + +static void sccp_cc_timeout(void *_data) +{ + struct osmo_bsc_sccp_con *data = + (struct osmo_bsc_sccp_con *) _data; + + if (data->sccp->connection_state >= SCCP_CONNECTION_STATE_ESTABLISHED) + return; + + LOGP(DMSC, LOGL_ERROR, "The connection was never established.\n"); + bsc_sccp_force_free(data); +} + +static void msc_sccp_write_ipa(struct sccp_connection *conn, struct msgb *msg, + void *global_ctx, void *ctx) +{ + struct bsc_msc_connection *msc_con; + + if (conn) { + struct osmo_bsc_sccp_con *bsc_con = conn->data_ctx; + msc_con = bsc_con->msc->msc_con; + if (bsc_con->send_ping) { + bsc_con->send_ping = 0; + msc_queue_write_with_ping(msc_con, msg, IPAC_PROTO_SCCP); + return; + } + } else { + msc_con = ctx; + } + + msc_queue_write(msc_con, msg, IPAC_PROTO_SCCP); +} + +static int msc_sccp_accept(struct sccp_connection *connection, void *data) +{ + LOGP(DMSC, LOGL_DEBUG, "Rejecting incoming SCCP connection.\n"); + return -1; +} + +static int msc_sccp_read(struct msgb *msgb, unsigned int length, void *data) +{ + struct bsc_msc_data *msc = (struct bsc_msc_data *) msgb->cb[0]; + return bsc_handle_udt(msc, msgb, length); +} + +int bsc_queue_for_msc(struct osmo_bsc_sccp_con *conn, struct msgb *msg) +{ + struct sccp_connection *sccp = conn->sccp; + + if (sccp->connection_state > SCCP_CONNECTION_STATE_ESTABLISHED) { + LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp); + msgb_free(msg); + } else if (sccp->connection_state == SCCP_CONNECTION_STATE_ESTABLISHED + && conn->sccp_queue_size == 0) { + sccp_connection_write(sccp, msg); + msgb_free(msg); + } else if (conn->sccp_queue_size > 10) { + LOGP(DMSC, LOGL_ERROR, "Connection closing, dropping packet on: %p\n", sccp); + msgb_free(msg); + } else { + LOGP(DMSC, LOGL_DEBUG, "Queueing packet on %p. Queue size: %d\n", sccp, conn->sccp_queue_size); + conn->sccp_queue_size += 1; + msgb_enqueue(&conn->sccp_queue, msg); + } + + return 0; +} + +enum bsc_con bsc_create_new_connection(struct gsm_subscriber_connection *conn, + struct bsc_msc_data *msc, int send_ping) +{ + struct osmo_bsc_sccp_con *bsc_con; + struct sccp_connection *sccp; + + /* This should not trigger */ + if (!msc || !msc->msc_con->is_authenticated) { + LOGP(DMSC, LOGL_ERROR, + "How did this happen? MSC is not connected. Dropping.\n"); + return BSC_CON_REJECT_NO_LINK; + } + + if (!bsc_grace_allow_new_connection(conn->bts->network, conn->bts)) { + LOGP(DMSC, LOGL_NOTICE, "BSC in grace period. No new connections.\n"); + return BSC_CON_REJECT_RF_GRACE; + } + + sccp = sccp_connection_socket(); + if (!sccp) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate memory.\n"); + return BSC_CON_NO_MEM; + } + + bsc_con = talloc_zero(conn->bts, struct osmo_bsc_sccp_con); + if (!bsc_con) { + LOGP(DMSC, LOGL_ERROR, "Failed to allocate.\n"); + sccp_connection_free(sccp); + return BSC_CON_NO_MEM; + } + + /* callbacks */ + sccp->state_cb = msc_outgoing_sccp_state; + sccp->data_cb = msc_outgoing_sccp_data; + sccp->data_ctx = bsc_con; + + bsc_con->send_ping = send_ping; + + /* prepare the timers */ + osmo_timer_setup(&bsc_con->sccp_it_timeout, sccp_it_timeout, bsc_con); + osmo_timer_setup(&bsc_con->sccp_cc_timeout, sccp_cc_timeout, bsc_con); + + INIT_LLIST_HEAD(&bsc_con->sccp_queue); + + bsc_con->sccp = sccp; + bsc_con->msc = msc; + bsc_con->conn = conn; + llist_add_tail(&bsc_con->entry, &active_connections); + conn->sccp_con = bsc_con; + return BSC_CON_SUCCESS; +} + +int bsc_open_connection(struct osmo_bsc_sccp_con *conn, struct msgb *msg) +{ + osmo_timer_schedule(&conn->sccp_cc_timeout, 10, 0); + sccp_connection_connect(conn->sccp, &sccp_ssn_bssap, msg); + msgb_free(msg); + return 0; +} + +int bsc_delete_connection(struct osmo_bsc_sccp_con *sccp) +{ + if (!sccp) + return 0; + + if (sccp->conn) + LOGP(DMSC, LOGL_ERROR, "Should have been cleared.\n"); + + llist_del(&sccp->entry); + osmo_timer_del(&sccp->sccp_it_timeout); + osmo_timer_del(&sccp->sccp_cc_timeout); + talloc_free(sccp); + return 0; +} + +static void bsc_notify_msc_lost(struct osmo_bsc_sccp_con *con) +{ + struct gsm_subscriber_connection *conn = con->conn; + + /* send USSD notification if string configured and con->data is set */ + if (!conn) + return; + + /* check for config string */ + if (!con->msc->ussd_msc_lost_txt) + return; + if (con->msc->ussd_msc_lost_txt[0] == '\0') + return; + + /* send USSD notification */ + bsc_send_ussd_notify(conn, 1, conn->sccp_con->msc->ussd_msc_lost_txt); + bsc_send_ussd_release_complete(conn); +} + +static void bsc_notify_and_close_conns(struct bsc_msc_connection *msc_con) +{ + struct osmo_bsc_sccp_con *con, *tmp; + + llist_for_each_entry_safe(con, tmp, &active_connections, entry) { + if (con->msc->msc_con != msc_con) + continue; + + bsc_notify_msc_lost(con); + bsc_sccp_force_free(con); + } +} + +static int handle_msc_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct msc_signal_data *msc; + + if (subsys != SS_MSC) + return 0; + + msc = signal_data; + if (signal == S_MSC_LOST) + bsc_notify_and_close_conns(msc->data->msc_con); + + return 0; +} + +int osmo_bsc_sccp_init(struct gsm_network *gsmnet) +{ + sccp_set_log_area(DSCCP); + sccp_system_init(msc_sccp_write_ipa, gsmnet); + sccp_connection_set_incoming(&sccp_ssn_bssap, msc_sccp_accept, NULL); + sccp_set_read(&sccp_ssn_bssap, msc_sccp_read, gsmnet); + + osmo_signal_register_handler(SS_MSC, handle_msc_signal, gsmnet); + + return 0; +} diff --git a/src/osmo-bsc/osmo_bsc_vty.c b/src/osmo-bsc/osmo_bsc_vty.c new file mode 100644 index 000000000..2e2e99bfd --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_vty.c @@ -0,0 +1,945 @@ +/* Osmo BSC VTY Configuration */ +/* (C) 2009-2015 by Holger Hans Peter Freyther + * (C) 2009-2014 by On-Waves + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <openbsc/gsm_data.h> +#include <openbsc/osmo_bsc.h> +#include <openbsc/bsc_msc_data.h> +#include <openbsc/vty.h> +#include <openbsc/bsc_subscriber.h> +#include <openbsc/debug.h> +#include <openbsc/bsc_msg_filter.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/vty/logging.h> + +#include <time.h> + + +#define IPA_STR "IP.ACCESS specific\n" + +extern struct gsm_network *bsc_gsmnet; + +static struct osmo_bsc_data *osmo_bsc_data(struct vty *vty) +{ + return bsc_gsmnet->bsc_data; +} + +static struct bsc_msc_data *bsc_msc_data(struct vty *vty) +{ + return vty->index; +} + +static struct cmd_node bsc_node = { + BSC_NODE, + "%s(config-bsc)# ", + 1, +}; + +static struct cmd_node msc_node = { + MSC_NODE, + "%s(config-msc)# ", + 1, +}; + +DEFUN(cfg_net_msc, cfg_net_msc_cmd, + "msc [<0-1000>]", "Configure MSC details\n" "MSC connection to configure\n") +{ + int index = argc == 1 ? atoi(argv[0]) : 0; + struct bsc_msc_data *msc; + + msc = osmo_msc_data_alloc(bsc_gsmnet, index); + if (!msc) { + vty_out(vty, "%%Failed to allocate MSC data.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = msc; + vty->node = MSC_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc, cfg_net_bsc_cmd, + "bsc", "Configure BSC\n") +{ + vty->node = BSC_NODE; + return CMD_SUCCESS; +} + +static void write_msc_amr_options(struct vty *vty, struct bsc_msc_data *msc) +{ +#define WRITE_AMR(vty, msc, name, var) \ + vty_out(vty, " amr-config %s %s%s", \ + name, msc->amr_conf.var ? "allowed" : "forbidden", \ + VTY_NEWLINE); + + WRITE_AMR(vty, msc, "12_2k", m12_2); + WRITE_AMR(vty, msc, "10_2k", m10_2); + WRITE_AMR(vty, msc, "7_95k", m7_95); + WRITE_AMR(vty, msc, "7_40k", m7_40); + WRITE_AMR(vty, msc, "6_70k", m6_70); + WRITE_AMR(vty, msc, "5_90k", m5_90); + WRITE_AMR(vty, msc, "5_15k", m5_15); + WRITE_AMR(vty, msc, "4_75k", m4_75); +#undef WRITE_AMR +} + +static void write_msc(struct vty *vty, struct bsc_msc_data *msc) +{ + struct bsc_msc_dest *dest; + + vty_out(vty, "msc %d%s", msc->nr, VTY_NEWLINE); + if (msc->bsc_token) + vty_out(vty, " token %s%s", msc->bsc_token, VTY_NEWLINE); + if (msc->bsc_key_present) + vty_out(vty, " auth-key %s%s", + osmo_hexdump(msc->bsc_key, sizeof(msc->bsc_key)), VTY_NEWLINE); + if (msc->core_mnc != -1) + vty_out(vty, " core-mobile-network-code %d%s", + msc->core_mnc, VTY_NEWLINE); + if (msc->core_mcc != -1) + vty_out(vty, " core-mobile-country-code %d%s", + msc->core_mcc, VTY_NEWLINE); + if (msc->core_lac != -1) + vty_out(vty, " core-location-area-code %d%s", + msc->core_lac, VTY_NEWLINE); + if (msc->core_ci != -1) + vty_out(vty, " core-cell-identity %d%s", + msc->core_ci, VTY_NEWLINE); + vty_out(vty, " ip.access rtp-base %d%s", msc->rtp_base, VTY_NEWLINE); + + if (msc->ping_timeout == -1) + vty_out(vty, " no timeout-ping%s", VTY_NEWLINE); + else { + vty_out(vty, " timeout-ping %d%s", msc->ping_timeout, VTY_NEWLINE); + vty_out(vty, " timeout-pong %d%s", msc->pong_timeout, VTY_NEWLINE); + if (msc->advanced_ping) + vty_out(vty, " timeout-ping advanced%s", VTY_NEWLINE); + else + vty_out(vty, " no timeout-ping advanced%s", VTY_NEWLINE); + } + + if (msc->ussd_welcome_txt) + vty_out(vty, " bsc-welcome-text %s%s", msc->ussd_welcome_txt, VTY_NEWLINE); + else + vty_out(vty, " no bsc-welcome-text%s", VTY_NEWLINE); + + if (msc->ussd_msc_lost_txt && msc->ussd_msc_lost_txt[0]) + vty_out(vty, " bsc-msc-lost-text %s%s", msc->ussd_msc_lost_txt, VTY_NEWLINE); + else + vty_out(vty, " no bsc-msc-lost-text%s", VTY_NEWLINE); + + if (msc->ussd_grace_txt && msc->ussd_grace_txt[0]) + vty_out(vty, " bsc-grace-text %s%s", msc->ussd_grace_txt, VTY_NEWLINE); + else + vty_out(vty, " no bsc-grace-text%s", VTY_NEWLINE); + + if (msc->audio_length != 0) { + int i; + + vty_out(vty, " codec-list "); + for (i = 0; i < msc->audio_length; ++i) { + if (i != 0) + vty_out(vty, " "); + + if (msc->audio_support[i]->hr) + vty_out(vty, "hr%.1u", msc->audio_support[i]->ver); + else + vty_out(vty, "fr%.1u", msc->audio_support[i]->ver); + } + vty_out(vty, "%s", VTY_NEWLINE); + + } + + llist_for_each_entry(dest, &msc->dests, list) + vty_out(vty, " dest %s %d %d%s", dest->ip, dest->port, + dest->dscp, VTY_NEWLINE); + + vty_out(vty, " type %s%s", msc->type == MSC_CON_TYPE_NORMAL ? + "normal" : "local", VTY_NEWLINE); + vty_out(vty, " allow-emergency %s%s", msc->allow_emerg ? + "allow" : "deny", VTY_NEWLINE); + + if (msc->local_pref) + vty_out(vty, " local-prefix %s%s", msc->local_pref, VTY_NEWLINE); + + if (msc->acc_lst_name) + vty_out(vty, " access-list-name %s%s", msc->acc_lst_name, VTY_NEWLINE); + + /* write amr options */ + write_msc_amr_options(vty, msc); +} + +static int config_write_msc(struct vty *vty) +{ + struct bsc_msc_data *msc; + struct osmo_bsc_data *bsc = osmo_bsc_data(vty); + + llist_for_each_entry(msc, &bsc->mscs, entry) + write_msc(vty, msc); + + return CMD_SUCCESS; +} + +static int config_write_bsc(struct vty *vty) +{ + struct osmo_bsc_data *bsc = osmo_bsc_data(vty); + + vty_out(vty, "bsc%s", VTY_NEWLINE); + if (bsc->mid_call_txt) + vty_out(vty, " mid-call-text %s%s", bsc->mid_call_txt, VTY_NEWLINE); + vty_out(vty, " mid-call-timeout %d%s", bsc->mid_call_timeout, VTY_NEWLINE); + if (bsc->rf_ctrl_name) + vty_out(vty, " bsc-rf-socket %s%s", + bsc->rf_ctrl_name, VTY_NEWLINE); + + if (bsc->auto_off_timeout != -1) + vty_out(vty, " bsc-auto-rf-off %d%s", + bsc->auto_off_timeout, VTY_NEWLINE); + + if (bsc->ussd_no_msc_txt && bsc->ussd_no_msc_txt[0]) + vty_out(vty, " missing-msc-text %s%s", bsc->ussd_no_msc_txt, VTY_NEWLINE); + else + vty_out(vty, " no missing-msc-text%s", VTY_NEWLINE); + if (bsc->acc_lst_name) + vty_out(vty, " access-list-name %s%s", bsc->acc_lst_name, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_token, + cfg_net_bsc_token_cmd, + "token TOKEN", + "A token for the BSC to be sent to the MSC\n" "A token\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + osmo_talloc_replace_string(osmo_bsc_data(vty), &data->bsc_token, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_key, + cfg_net_bsc_key_cmd, + "auth-key KEY", + "Authentication (secret) key configuration\n" + "Security key\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + osmo_hexparse(argv[0], data->bsc_key, sizeof(data->bsc_key)); + data->bsc_key_present = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_no_bsc_key, cfg_net_bsc_no_key_cmd, + "no auth-key", + NO_STR "Authentication (secret) key configuration\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + memset(data->bsc_key, 0, sizeof(data->bsc_key)); + data->bsc_key_present = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_ncc, + cfg_net_bsc_ncc_cmd, + "core-mobile-network-code <1-999>", + "Use this network code for the core network\n" "MNC value\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->core_mnc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_mcc, + cfg_net_bsc_mcc_cmd, + "core-mobile-country-code <1-999>", + "Use this country code for the core network\n" "MCC value\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->core_mcc = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_lac, + cfg_net_bsc_lac_cmd, + "core-location-area-code <0-65535>", + "Use this location area code for the core network\n" "LAC value\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->core_lac = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_ci, + cfg_net_bsc_ci_cmd, + "core-cell-identity <0-65535>", + "Use this cell identity for the core network\n" "CI value\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->core_ci = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_rtp_base, + cfg_net_bsc_rtp_base_cmd, + "ip.access rtp-base <1-65000>", + IPA_STR + "Set the rtp-base port for the RTP stream\n" + "Port number\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->rtp_base = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_codec_list, + cfg_net_bsc_codec_list_cmd, + "codec-list .LIST", + "Set the allowed audio codecs\n" + "List of audio codecs, e.g. fr3 fr1 hr3\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + int saw_fr, saw_hr; + int i; + + saw_fr = saw_hr = 0; + + /* free the old list... if it exists */ + if (data->audio_support) { + talloc_free(data->audio_support); + data->audio_support = NULL; + data->audio_length = 0; + } + + /* create a new array */ + data->audio_support = + talloc_zero_array(osmo_bsc_data(vty), struct gsm_audio_support *, argc); + data->audio_length = argc; + + for (i = 0; i < argc; ++i) { + /* check for hrX or frX */ + if (strlen(argv[i]) != 3 + || argv[i][1] != 'r' + || (argv[i][0] != 'h' && argv[i][0] != 'f') + || argv[i][2] < 0x30 + || argv[i][2] > 0x39) + goto error; + + data->audio_support[i] = talloc_zero(data->audio_support, + struct gsm_audio_support); + data->audio_support[i]->ver = atoi(argv[i] + 2); + + if (strncmp("hr", argv[i], 2) == 0) { + data->audio_support[i]->hr = 1; + saw_hr = 1; + } else if (strncmp("fr", argv[i], 2) == 0) { + data->audio_support[i]->hr = 0; + saw_fr = 1; + } + + if (saw_hr && saw_fr) { + vty_out(vty, "Can not have full-rate and half-rate codec.%s", + VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; + } + } + + return CMD_SUCCESS; + +error: + vty_out(vty, "Codec name must be hrX or frX. Was '%s'%s", + argv[i], VTY_NEWLINE); + return CMD_ERR_INCOMPLETE; +} + +DEFUN(cfg_net_msc_dest, + cfg_net_msc_dest_cmd, + "dest A.B.C.D <1-65000> <0-255>", + "Add a destination to a MUX/MSC\n" + "IP Address\n" "Port\n" "DSCP\n") +{ + struct bsc_msc_dest *dest; + struct bsc_msc_data *data = bsc_msc_data(vty); + + dest = talloc_zero(osmo_bsc_data(vty), struct bsc_msc_dest); + if (!dest) { + vty_out(vty, "%%Failed to create structure.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + dest->ip = talloc_strdup(dest, argv[0]); + if (!dest->ip) { + vty_out(vty, "%%Failed to copy dest ip.%s", VTY_NEWLINE); + talloc_free(dest); + return CMD_WARNING; + } + + dest->port = atoi(argv[1]); + dest->dscp = atoi(argv[2]); + llist_add_tail(&dest->list, &data->dests); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_no_dest, + cfg_net_msc_no_dest_cmd, + "no dest A.B.C.D <1-65000> <0-255>", + NO_STR "Remove a destination to a MUX/MSC\n" + "IP Address\n" "Port\n" "DSCP\n") +{ + struct bsc_msc_dest *dest, *tmp; + struct bsc_msc_data *data = bsc_msc_data(vty); + + int port = atoi(argv[1]); + int dscp = atoi(argv[2]); + + llist_for_each_entry_safe(dest, tmp, &data->dests, list) { + if (port != dest->port || dscp != dest->dscp + || strcmp(dest->ip, argv[0]) != 0) + continue; + + llist_del(&dest->list); + talloc_free(dest); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_no_ping_time, + cfg_net_msc_no_ping_time_cmd, + "no timeout-ping", + NO_STR "Disable the ping/pong handling on A-link\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->ping_timeout = -1; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_ping_time, + cfg_net_msc_ping_time_cmd, + "timeout-ping <1-2147483647>", + "Set the PING interval, negative for not sending PING\n" + "Timeout in seconds\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->ping_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_pong_time, + cfg_net_msc_pong_time_cmd, + "timeout-pong <1-2147483647>", + "Set the time to wait for a PONG\n" "Timeout in seconds\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->pong_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_advanced_ping, + cfg_net_msc_advanced_ping_cmd, + "timeout-ping advanced", + "Ping timeout handling\nEnable advanced mode during SCCP\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + if (data->ping_timeout == -1) { + vty_out(vty, "%%ping handling is disabled. Enable it first.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + data->advanced_ping = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_net_msc_advanced_ping, + cfg_no_net_msc_advanced_ping_cmd, + "no timeout-ping advanced", + NO_STR "Ping timeout handling\nEnable advanced mode during SCCP\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->advanced_ping = 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_welcome_ussd, + cfg_net_msc_welcome_ussd_cmd, + "bsc-welcome-text .TEXT", + "Set the USSD notification to be sent\n" "Text to be sent\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + char *str = argv_concat(argv, argc, 0); + if (!str) + return CMD_WARNING; + + osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_welcome_txt, str); + talloc_free(str); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_no_welcome_ussd, + cfg_net_msc_no_welcome_ussd_cmd, + "no bsc-welcome-text", + NO_STR "Clear the USSD notification to be sent\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + talloc_free(data->ussd_welcome_txt); + data->ussd_welcome_txt = NULL; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_lost_ussd, + cfg_net_msc_lost_ussd_cmd, + "bsc-msc-lost-text .TEXT", + "Set the USSD notification to be sent on MSC connection loss\n" "Text to be sent\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + char *str = argv_concat(argv, argc, 0); + if (!str) + return CMD_WARNING; + + osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_msc_lost_txt, str); + talloc_free(str); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_no_lost_ussd, + cfg_net_msc_no_lost_ussd_cmd, + "no bsc-msc-lost-text", + NO_STR "Clear the USSD notification to be sent on MSC connection loss\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + talloc_free(data->ussd_msc_lost_txt); + data->ussd_msc_lost_txt = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_grace_ussd, + cfg_net_msc_grace_ussd_cmd, + "bsc-grace-text .TEXT", + "Set the USSD notification to be sent when the MSC has entered the grace period\n" "Text to be sent\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + char *str = argv_concat(argv, argc, 0); + if (!str) + return CMD_WARNING; + + osmo_talloc_replace_string(osmo_bsc_data(vty), &data->ussd_grace_txt, str); + talloc_free(str); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_no_grace_ussd, + cfg_net_msc_no_grace_ussd_cmd, + "no bsc-grace-text", + NO_STR "Clear the USSD notification to be sent when the MSC has entered the grace period\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + talloc_free(data->ussd_grace_txt); + data->ussd_grace_txt = NULL; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_missing_msc_ussd, + cfg_net_bsc_missing_msc_ussd_cmd, + "missing-msc-text .TEXT", + "Set the USSD notification to be send when a MSC has not been found.\n" "Text to be sent\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(data, &data->ussd_no_msc_txt, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_no_missing_msc_text, + cfg_net_bsc_no_missing_msc_text_cmd, + "no missing-msc-text", + NO_STR "Clear the USSD notification to be send when a MSC has not been found.\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + + talloc_free(data->ussd_no_msc_txt); + data->ussd_no_msc_txt = 0; + + return CMD_SUCCESS; +} + + +DEFUN(cfg_net_msc_type, + cfg_net_msc_type_cmd, + "type (normal|local)", + "Select the MSC type\n" + "Plain GSM MSC\n" "Special MSC for local call routing\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + + if (strcmp(argv[0], "normal") == 0) + data->type = MSC_CON_TYPE_NORMAL; + else if (strcmp(argv[0], "local") == 0) + data->type = MSC_CON_TYPE_LOCAL; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_emerg, + cfg_net_msc_emerg_cmd, + "allow-emergency (allow|deny)", + "Allow CM ServiceRequests with type emergency\n" + "Allow\n" "Deny\n") +{ + struct bsc_msc_data *data = bsc_msc_data(vty); + data->allow_emerg = strcmp("allow", argv[0]) == 0; + return CMD_SUCCESS; +} + +DEFUN(cfg_net_msc_local_prefix, + cfg_net_msc_local_prefix_cmd, + "local-prefix REGEXP", + "Prefix for local numbers\n" "REGEXP used\n") +{ + struct bsc_msc_data *msc = bsc_msc_data(vty); + + if (gsm_parse_reg(msc, &msc->local_pref_reg, &msc->local_pref, argc, argv) != 0) { + vty_out(vty, "%%Failed to parse the regexp: '%s'%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +#define AMR_CONF_STR "AMR Multirate Configuration\n" +#define AMR_COMMAND(name) \ + DEFUN(cfg_net_msc_amr_##name, \ + cfg_net_msc_amr_##name##_cmd, \ + "amr-config " #name "k (allowed|forbidden)", \ + AMR_CONF_STR "Bitrate\n" "Allowed\n" "Forbidden\n") \ +{ \ + struct bsc_msc_data *msc = bsc_msc_data(vty); \ + \ + msc->amr_conf.m##name = strcmp(argv[0], "allowed") == 0; \ + return CMD_SUCCESS; \ +} + +AMR_COMMAND(12_2) +AMR_COMMAND(10_2) +AMR_COMMAND(7_95) +AMR_COMMAND(7_40) +AMR_COMMAND(6_70) +AMR_COMMAND(5_90) +AMR_COMMAND(5_15) +AMR_COMMAND(4_75) + +DEFUN(cfg_msc_acc_lst_name, + cfg_msc_acc_lst_name_cmd, + "access-list-name NAME", + "Set the name of the access list to use.\n" + "The name of the to be used access list.") +{ + struct bsc_msc_data *msc = bsc_msc_data(vty); + + osmo_talloc_replace_string(msc, &msc->acc_lst_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_msc_no_acc_lst_name, + cfg_msc_no_acc_lst_name_cmd, + "no access-list-name", + NO_STR "Remove the access list from the NAT.\n") +{ + struct bsc_msc_data *msc = bsc_msc_data(vty); + + if (msc->acc_lst_name) { + talloc_free(msc->acc_lst_name); + msc->acc_lst_name = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_mid_call_text, + cfg_net_bsc_mid_call_text_cmd, + "mid-call-text .TEXT", + "Set the USSD notification to be send.\n" "Text to be sent\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + char *txt = argv_concat(argv, argc, 0); + if (!txt) + return CMD_WARNING; + + osmo_talloc_replace_string(data, &data->mid_call_txt, txt); + talloc_free(txt); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_bsc_mid_call_timeout, + cfg_net_bsc_mid_call_timeout_cmd, + "mid-call-timeout NR", + "Switch from Grace to Off in NR seconds.\n" "Timeout in seconds\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + data->mid_call_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_rf_socket, + cfg_net_rf_socket_cmd, + "bsc-rf-socket PATH", + "Set the filename for the RF control interface.\n" "RF Control path\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + + osmo_talloc_replace_string(data, &data->rf_ctrl_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_rf_off_time, + cfg_net_rf_off_time_cmd, + "bsc-auto-rf-off <1-65000>", + "Disable RF on MSC Connection\n" "Timeout\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + data->auto_off_timeout = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_no_rf_off_time, + cfg_net_no_rf_off_time_cmd, + "no bsc-auto-rf-off", + NO_STR "Disable RF on MSC Connection\n") +{ + struct osmo_bsc_data *data = osmo_bsc_data(vty); + data->auto_off_timeout = -1; + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_acc_lst_name, + cfg_bsc_acc_lst_name_cmd, + "access-list-name NAME", + "Set the name of the access list to use.\n" + "The name of the to be used access list.") +{ + struct osmo_bsc_data *bsc = osmo_bsc_data(vty); + + osmo_talloc_replace_string(bsc, &bsc->acc_lst_name, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bsc_no_acc_lst_name, + cfg_bsc_no_acc_lst_name_cmd, + "no access-list-name", + NO_STR "Remove the access list from the BSC\n") +{ + struct osmo_bsc_data *bsc = osmo_bsc_data(vty); + + if (bsc->acc_lst_name) { + talloc_free(bsc->acc_lst_name); + bsc->acc_lst_name = NULL; + } + + return CMD_SUCCESS; +} + +DEFUN(show_statistics, + show_statistics_cmd, + "show statistics", + SHOW_STR "Statistics about the BSC\n") +{ + openbsc_vty_print_statistics(vty, bsc_gsmnet); + return CMD_SUCCESS; +} + +DEFUN(show_mscs, + show_mscs_cmd, + "show mscs", + SHOW_STR "MSC Connections and State\n") +{ + struct bsc_msc_data *msc; + llist_for_each_entry(msc, &bsc_gsmnet->bsc_data->mscs, entry) { + vty_out(vty, "MSC Nr: %d is connected: %d auth: %d.%s", + msc->nr, + msc->msc_con ? msc->msc_con->is_connected : -1, + msc->msc_con ? msc->msc_con->is_authenticated : -1, + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(show_pos, + show_pos_cmd, + "show position", + SHOW_STR "Position information of the BTS\n") +{ + struct gsm_bts *bts; + struct bts_location *curloc; + struct tm time; + char timestr[50]; + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + if (llist_empty(&bts->loc_list)) { + vty_out(vty, "BTS Nr: %d position invalid%s", bts->nr, + VTY_NEWLINE); + continue; + } + curloc = llist_entry(bts->loc_list.next, struct bts_location, list); + if (gmtime_r(&curloc->tstamp, &time) == NULL) { + vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr, + VTY_NEWLINE); + continue; + } + if (asctime_r(&time, timestr) == NULL) { + vty_out(vty, "Time conversion failed for BTS %d%s", bts->nr, + VTY_NEWLINE); + continue; + } + /* Last character in asctime is \n */ + timestr[strlen(timestr)-1] = 0; + + vty_out(vty, "BTS Nr: %d position: %s time: %s%s", bts->nr, + get_value_string(bts_loc_fix_names, curloc->valid), timestr, + VTY_NEWLINE); + vty_out(vty, " lat: %f lon: %f height: %f%s", curloc->lat, curloc->lon, + curloc->height, VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +DEFUN(gen_position_trap, + gen_position_trap_cmd, + "generate-location-state-trap <0-255>", + "Generate location state report\n" + "BTS to report\n") +{ + int bts_nr; + struct gsm_bts *bts; + struct gsm_network *net = bsc_gsmnet; + + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(net, bts_nr); + bsc_gen_location_state_trap(bts); + return CMD_SUCCESS; +} + +DEFUN(logging_fltr_imsi, + logging_fltr_imsi_cmd, + "logging filter imsi IMSI", + LOGGING_STR FILTER_STR + "Filter log messages by IMSI\n" "IMSI to be used as filter\n") +{ + struct bsc_subscr *bsc_subscr; + struct log_target *tgt = osmo_log_vty2tgt(vty); + const char *imsi = argv[0]; + + bsc_subscr = bsc_subscr_find_by_imsi(bsc_gsmnet->bsc_subscribers, imsi); + + if (!bsc_subscr) { + vty_out(vty, "%%no subscriber with IMSI(%s)%s", + imsi, VTY_NEWLINE); + return CMD_WARNING; + } + + log_set_filter_bsc_subscr(tgt, bsc_subscr); + return CMD_SUCCESS; +} + +int bsc_vty_init_extra(void) +{ + install_element(CONFIG_NODE, &cfg_net_msc_cmd); + install_element(CONFIG_NODE, &cfg_net_bsc_cmd); + + install_node(&bsc_node, config_write_bsc); + vty_install_default(BSC_NODE); + install_element(BSC_NODE, &cfg_net_bsc_mid_call_text_cmd); + install_element(BSC_NODE, &cfg_net_bsc_mid_call_timeout_cmd); + install_element(BSC_NODE, &cfg_net_rf_socket_cmd); + install_element(BSC_NODE, &cfg_net_rf_off_time_cmd); + install_element(BSC_NODE, &cfg_net_no_rf_off_time_cmd); + install_element(BSC_NODE, &cfg_net_bsc_missing_msc_ussd_cmd); + install_element(BSC_NODE, &cfg_net_bsc_no_missing_msc_text_cmd); + install_element(BSC_NODE, &cfg_bsc_acc_lst_name_cmd); + install_element(BSC_NODE, &cfg_bsc_no_acc_lst_name_cmd); + + install_node(&msc_node, config_write_msc); + vty_install_default(MSC_NODE); + install_element(MSC_NODE, &cfg_net_bsc_token_cmd); + install_element(MSC_NODE, &cfg_net_bsc_key_cmd); + install_element(MSC_NODE, &cfg_net_bsc_no_key_cmd); + install_element(MSC_NODE, &cfg_net_bsc_ncc_cmd); + install_element(MSC_NODE, &cfg_net_bsc_mcc_cmd); + install_element(MSC_NODE, &cfg_net_bsc_lac_cmd); + install_element(MSC_NODE, &cfg_net_bsc_ci_cmd); + install_element(MSC_NODE, &cfg_net_bsc_rtp_base_cmd); + install_element(MSC_NODE, &cfg_net_bsc_codec_list_cmd); + install_element(MSC_NODE, &cfg_net_msc_dest_cmd); + install_element(MSC_NODE, &cfg_net_msc_no_dest_cmd); + install_element(MSC_NODE, &cfg_net_msc_no_ping_time_cmd); + install_element(MSC_NODE, &cfg_net_msc_ping_time_cmd); + install_element(MSC_NODE, &cfg_net_msc_pong_time_cmd); + install_element(MSC_NODE, &cfg_net_msc_advanced_ping_cmd); + install_element(MSC_NODE, &cfg_no_net_msc_advanced_ping_cmd); + install_element(MSC_NODE, &cfg_net_msc_welcome_ussd_cmd); + install_element(MSC_NODE, &cfg_net_msc_no_welcome_ussd_cmd); + install_element(MSC_NODE, &cfg_net_msc_lost_ussd_cmd); + install_element(MSC_NODE, &cfg_net_msc_no_lost_ussd_cmd); + install_element(MSC_NODE, &cfg_net_msc_grace_ussd_cmd); + install_element(MSC_NODE, &cfg_net_msc_no_grace_ussd_cmd); + install_element(MSC_NODE, &cfg_net_msc_type_cmd); + install_element(MSC_NODE, &cfg_net_msc_emerg_cmd); + install_element(MSC_NODE, &cfg_net_msc_local_prefix_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_12_2_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_10_2_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_7_95_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_7_40_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_6_70_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_5_90_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_5_15_cmd); + install_element(MSC_NODE, &cfg_net_msc_amr_4_75_cmd); + install_element(MSC_NODE, &cfg_msc_acc_lst_name_cmd); + install_element(MSC_NODE, &cfg_msc_no_acc_lst_name_cmd); + + install_element_ve(&show_statistics_cmd); + install_element_ve(&show_mscs_cmd); + install_element_ve(&show_pos_cmd); + install_element_ve(&logging_fltr_imsi_cmd); + + install_element(ENABLE_NODE, &gen_position_trap_cmd); + + install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd); + + return 0; +} |