diff options
Diffstat (limited to 'src/osmo-bsc/osmo_bsc_api.c')
-rw-r--r-- | src/osmo-bsc/osmo_bsc_api.c | 550 |
1 files changed, 550 insertions, 0 deletions
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; +} |